把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷3474】[POI2008] KUP-Plot purchase(悬线法)

题目链接

  • 给定一个 \(n\times n\) 的矩阵和一个整数 \(k\),要求找出任意一个元素和在 \([k,2k]\) 范围内的子矩阵,或判断不存在。
  • \(1\le n\le2\times10^3\)\(1\le k\le10^9\)\(0\le a_{i,j}\le2\times10^9\)

性质讨论

显然,如果子矩阵中包含大于\(2k\) 的元素,这个子矩阵的元素和肯定大于 \(2k\)

而如果我们想在一个所有元素都小于等于 \(2k\) 的子矩阵中寻找一个元素和在 \([k,2k]\) 范围内的子矩阵,假设元素和为 \(S\),根据 \(S\) 的大小讨论:

  • \(S < k\):显然无解。
  • \(k\le S\le 2k\):这个子矩阵就是答案。
  • \(S>2k\):不断将它拆分,则总有一个时刻会拆出两个小于等于 \(2k\) 的子矩阵,其中一个大于等于 \(k\),一个小于 \(k\),大于等于 \(k\) 且小于等于 \(2k\) 的那个可以作为答案。

综上,只要一个子矩阵中没有大于 \(2k\) 的元素且元素和大于等于 \(k\),就可以从中找出答案。

实际上我们只要找出所有没有大于 \(2k\) 的元素的极大子矩阵,如果它的元素和大于等于 \(k\) 就可以找出答案,否则无解。

悬线法找极大子矩阵

悬线法是在有障碍点的情况下求极大子矩阵的一个算法。

对于每个位置我们预处理出它能到的最上面的一行。

显然极大子矩阵中至少有一列顶到了它能到的最上面的一行,且它无法再向两侧扩展。

也就是说,我们可以去枚举每一行,对于这行的每个位置,利用单调栈求出选中了 从这个位置到它能到的最上面的位置(悬线)后,向左向右分别能扩展到哪一列。

一旦找到一个元素和大于等于 \(k\) 的子矩阵就可以去寻找答案了。

代码:\(O(n^2)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define LL long long
#define N 2000
#define S(A,B,C,D) (s[C][D]-s[(A)-1][D]-s[C][(B)-1]+s[(A)-1][(B)-1])
using namespace std;
int n,k,St[N+5],L[N+5],R[N+5],a[N+5][N+5],h[N+5][N+5];LL s[N+5][N+5];
I void Solve(CI A,CI B,CI C,CI D)//在(A,B)-(C,D)这个子矩阵中找答案
{
	RI t=S(A,B,C,D);if(t<k) return;if(t<=2*k) printf("%d %d %d %d\n",A,B,C,D),exit(0);
	if(A^C) {RI m=A+C>>1;return Solve(A,B,m,D),Solve(m+1,B,C,D);}RI m=B+D>>1;return Solve(A,B,C,m),Solve(A,m+1,C,D);//分割,实际上可以一行一列地删
}
int main()
{
	RI i,j;for(scanf("%d%d",&k,&n),i=1;i<=n;++i) for(j=1;j<=n;++j) scanf("%d",&a[j][i]);
	for(i=1;i<=n;++i) for(j=1;j<=n;++j) s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];//二维前缀和
	for(j=1;j<=n;++j) h[0][j]=1;for(i=1;i<=n;++i) for(j=1;j<=n;++j) h[i][j]=a[i][j]<=2*k?h[i-1][j]:i+1;//预处理每个位置能到的最上面一行
	RI T;for(i=1;i<=n;++i)//枚举每一行
	{
		for(h[i][St[T=1]=0]=n+2,j=1;j<=n;L[j]=St[T]+1,St[++T]=j++) W(h[i][St[T]]<=h[i][j]) --T;//求出每根悬线向左最多到哪一列
		for(h[i][St[T=1]=n+1]=n+2,j=n;j;R[j]=St[T]-1,St[++T]=j--) W(h[i][St[T]]<=h[i][j]) --T;//求出每根悬线向右最多到哪一列
		for(j=1;j<=n;++j) S(h[i][j],L[j],i,R[j])>=k&&(Solve(h[i][j],L[j],i,R[j]),0);//判断是否存在元素和大于k的极大子矩阵
	}return puts("NIE"),0;
}
posted @ 2021-10-06 17:57  TheLostWeak  阅读(42)  评论(0编辑  收藏  举报