传送门:bzoj1127
zz选手做POI实属吃力。。。
悬线法都忘了怎么写了。。。
题解
首先判掉存在单独元素满足 K ≤ a i , j ≤ 2 K K\leq a_{i,j}\leq 2K K≤ai,j≤2K的情况。
那么现在所有 a i , j a_{i,j} ai,j要么 < K <K <K,要么 > 2 K >2K >2K。
所有 > 2 K >2K >2K的直接舍去,考虑悬线法求出任意只含 < K <K <K元素且总值 ≥ K \geq K ≥K的子矩阵:
若矩阵总值
≤
2
K
\leq 2K
≤2K,直接合法。
否则由于每个元素均
<
K
<K
<K,不断地弹出必能达到
≤
2
K
\leq 2K
≤2K且
≥
K
\geq K
≥K的状态。
具体来说:
对于单行的矩阵,不断减小列数直到满足要求。
对于多行的矩阵,不断删去第一行:
若删去第一行后总值
≥
K
\geq K
≥K,不断迭代;
否则第一行总值必然
≥
K
\geq K
≥K,单独取出第一行处理即可。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=2002;
typedef long long ll;
int n,K,ql[N],qr[N],h[N];
bool b[N][N];
int stx,sty,edx,edy;
ll a[N][N],v;
inline ll ask(int xa,int ya,int xb,int yb)
{return a[xb][yb]-a[xa-1][yb]-a[xb][ya-1]+a[xa-1][ya-1];}
//悬线法
void calc()
{
int l,r,i,j;
for(i=1;i<=n;++i) ql[i]=1,qr[i]=n;
for(i=1;i<=n;++i){
l=0;r=n+1;
for(j=1;j<=n;++j){
if(b[i][j]) h[j]++,ql[j]=max(ql[j],l+1);
else h[j]=0,ql[j]=1,l=j;
}
for(j=n;j;--j){
if(b[i][j]) qr[j]=min(qr[j],r-1);
else qr[j]=n,r=j;
stx=i-h[j]+1;sty=ql[j];edx=i;edy=qr[j];
if((v=ask(stx,sty,edx,edy))>=K) return;
stx=0;
}
}
}
int main(){
int i,j,x;ll q;
scanf("%d%d",&K,&n);
for(i=1;i<=n;++i)
for(j=1;j<=n;++j){
scanf("%d",&x);
if(x>=K && x<=(K<<1)){
printf("%d %d %d %d",j,i,j,i);
return 0;
}
a[i][j]=a[i-1][j]+a[i][j-1]-a[i-1][j-1]+x;
b[i][j]= (x<=(K<<1));
}
calc();
if(!stx) {puts("NIE");return 0;}
for(;v>(K<<1);){
if(stx^edx){
if((q=ask(stx+1,sty,edx,edy))>=K) {stx++;v=q;continue;}
else {edx=stx;v=ask(stx,sty,edx,edy);continue;}
}else{
for(;;){
v=ask(stx,sty,edx,--edy);
if(v<=(K<<1)) break;
}
break;
}
}
printf("%d %d %d %d",sty,stx,edy,edx);
return 0;
}