CF1423M Milutin's Plums

题解

Posted by WrongAnswer_90's Blog on November 12, 2024

My Blogs

CF1423M Milutin’s Plums

完全单调性的定义:一个 $n\times m$ 的矩阵,如果对于所有 $S\subseteq \{ 1,2,3\dots n \} $,$T\subseteq \{1,2,3\dots m \}$,只保留 $S$ 中的行和 $T$ 中的列时,每行第一个最小值的位置都是非降的,则称其有完全单调性。

SMAWK 算法:一种能在线性的单点询问次数内,求出一个满足完全单调性矩阵每行最小值及其位置的算法。

外层分治

首先可以通过分治得到一个简单的 $\mathcal O((n+m)\log n)$ 的做法:把所有奇数行删掉之后求出所有偶数行的决策点,奇数行的决策点一定在相邻两个之间。每层询问 $\mathcal O(n+m)$ 次。

上面复杂度带 $\log$ 的原因在于,矩阵可能变得 $n$ 非常小,但是 $m$ 仍然非常大。

reduce 操作

注意到每一行只需要保留最小值第一次出现的位置,所以有效的列数是 $\min(n,m)$ 而不是 $m$。SMAWK 的 reduce 操作可以用 $\mathcal O(m)$ 次操作,通过删去全都不可能是最小值的列,将 $m$ 变成 $\min(n,m)$。

考虑询问 $(x,y)$ 和 $(x,y+1)$ 之间的大小关系可以知道什么。如果 $f(x,y)>f(x,y+1)$,取一个只保留 $y,y+1$ 列的子矩形,则 $(x,y)$ 不是最小值点。由完全单调性得,$\forall i\geq x,f(i,y)$ 不是第 $y$ 行对应的最小值点。

而如果 $f(x,y)\leq f(x,y+1)$,同理说明 $\forall i\leq x,f(i,y)$ 也不是最小值点。

考虑维护一个指针 $k$,初始为 $1$,不断询问 $f(k,k)$ 和 $f(k,k+1)$ 的关系。

如果 $f(k,k)\leq f(k,k+1)$,如果 $k=n$ 则删掉第 $k+1$ 行。否则令 $k\leftarrow (k+1)$。容易发现这样的操作保证了,每时每刻,$\forall i\leq j\leq k,f(i,j)$ 都不是最小值点,也就是 $k$ 上方的一个三角形都是没用的。

如果 $f(k,k)>f(k,k+1)$,因为 $k$ 所在列的 $[1,k]$ 的位置都是没用的,这次询问又得到了 $k$ 下方的部分没用,所以删掉第 $k$ 列,令 $k\leftarrow \max(1,k-1)$。

这样分治的时候,每层先调用 reduce 操作,再按照一开始的分治运行即可。实现的时候可以封装一个交互的询问函数,记忆化已经问过的位置来减小常数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
int n,m;
int a[1010][1010];
unordered_map<ull,int> hash;
#define id(x,y) 1000000ull*x+y
inline int get(int x,int y,int tp=0)
{
	if(hash.find(id(x,y))!=hash.end())
	return hash[id(x,y)];
	cout<<"? "<<x<<" "<<y<<endl;
	fflush(stdout);
	cin>>hash[id(x,y)];
//		hash[id(x,y)]=a[x][y];
	return hash[id(x,y)];
}

int nex[1000010],pre[1000010];
inline void init(int n)
{
	for(int i=0;i<n;++i)nex[i]=i+1;
	for(int i=1;i<=n;++i)pre[i]=i-1;
	pre[0]=0,nex[n]=n;
}
inline void del(int x)
{
	nex[pre[x]]=nex[x];
	pre[nex[x]]=pre[x];
}
vi solve(vi x,vi y)
{
	{
		for(int i=0;i+1<(int)y.size();++i)
		nex[y[i]]=y[i+1],pre[y[i+1]]=y[i];
		pre[y[0]]=0,nex[0]=y[0];
		nex[y.back()]=-1;
		int i=0,Y=y[0],tat=(int)y.size()-(int)x.size();
		while(tat>0)
		{
			if(get(x[i],Y,1)>get(x[i],nex[Y],1))
			{
				del(Y),--tat;
				if(i)Y=pre[Y],--i;
				else Y=nex[Y];
			}
			else
			{
				if(i+1==(int)x.size())
				del(nex[Y]),--tat;
				else Y=nex[Y],++i;
			}
		}
		y.clear();
		for(int i=0,j=nex[0];i<(int)x.size();++i)
		{
			if(j==-1)break;
			y.eb(j),j=nex[j];
		}
	}
	if(x.size()==1)return y;
	vi xx;
	for(int j=1;j<(int)x.size();j+=2)
	xx.eb(x[j]);
	vi pos=solve(xx,y);
	vi realpos,Pos(x.size());
	
	realpos.eb(0);
	for(int j=0,k=0;k<(int)xx.size();++j)
	{
		while(k<(int)xx.size()&&y[j]==pos[k])
		realpos.eb(j),++k;
	}
	realpos.eb((int)y.size()-1);
	int mx=inf,p=0;
	
	for(int j=0;j<(int)xx.size();++j)
	Pos[(j<<1)|1]=pos[j];
	for(int j=0;j<(int)x.size();j+=2)
	{
		mx=inf,p=-1;
		for(int k=realpos[j>>1];k<=realpos[(j>>1)+1];++k)
		if(Mmin(mx,get(x[j],y[k])))p=y[k];
		Pos[j]=p;
	}
	
	return Pos;
}
void mian()
{
	read(n,m);
//		for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)
//		read(a[i][j]);
	vi X,Y;
	for(int i=1;i<=n;++i)X.eb(i);
	for(int i=1;i<=m;++i)Y.eb(i);
	int ans=inf;
	vi pos=solve(X,Y);
	for(int i=0;i<n;++i)Mmin(ans,get(i+1,pos[i]));
	cout<<"! "<<ans<<endl;
}