JZOJ_1222《Kup》——一道劲题

先上题目:

description

首先你们得承认今天的题目很短很简洁。。。
然后,你们还得承认接下来这个题目的描述更加简洁!!!
Task:给出一个N*N(1≤N≤2000)的矩阵,还给出一个整数K。要你在给定的矩阵中
求一个子矩阵,这个子矩阵中所有数的和的范围要在[k,2*k] 这个区间。
如果有多个这样的子矩阵,请随便输出一个。

Input

第一行包含两个整数K 和N(1≤K≤10^8,1≤N≤2000)。其意义如题目描述!
接下来有N 行,每行有N 个数,表示题目给出的矩阵。矩阵中的数都是非负数,而且
不大于maxlongint。

Output

输出文件仅包含一行,四个整数,分别是你找出来的矩阵的左上角坐标和右下角坐标。
如果不存在这样的子矩阵,请输出0 0 0 0。

Sample input1

4 3
1 1 1
1 9 1
1 1 1

Sample input2

8 4
1 2 1 3
25 1 2 1
4 20 3 3
3 30 12 2

Sample input3

8 4
12 2 1 3
25 1 2 1
4 20 3 3
3 30 12 2

Sample output1

0 0 0 0

Sample output2

1 2 2 4

Sample output3

1 1 1 1

Data Constraint

对于30%的数据,1≤N≤5
对于60%的数据,1≤N≤60
对于100%的数据1≤N≤2000

Time Limits:

1000 ms

总结:

这道题是今天比赛的最后一题,也是我唯一没有切的题。
有一个非常非常简略的题解,ACfast学长大概太相信我们的能力了。
在一堆苍蝇讨论了半天后,最后的结果是请清华神犇来讲讲,他把锅给我们初二背╮(╯▽╰)╭。

必备常识:

矩阵前缀和:f[i][j]=f[i-1][j]+f[i][j-1]-f[i-1][j-1]+a[i][j]
则a[a-c,b-d]=f[c][d]-f[c][b-1]-f[a-1][d]+f[a-1][b-1]

不要问为什么,自己画个图看。


首先由于元素都是非负数,所以若有a[i][j]>2k,那么我们选的矩阵一定不能包括它。

对于一个不包括>2k点的矩阵,如果它的矩阵和>=k,那么它一定有一个子矩阵的和sum满足k<=sum<=2k,请读者先仔细想想为什么,后面会解释。

现在需要求最大子矩阵(不包括>2k的点),由于元素非负,所以这个矩阵一定是嵌在>2k点和边界之间。

我们可以预处理r[i][j],表示(i,j)这个点向右遇到的第一个>2k的点,如果没有即为n+1。

枚举一个左边界l,利用单调栈维护一个使r[i][l]严格>=的东东,那么如果以当前r[i][l]-1作为右边界,上边界就是栈中下面的那一个i’-1,至于下边界现在还不知道。但是当我们遇到新的一个r[j][l]< r[i][l]时,要把r[i][l]退栈时,就知道了下边界是j-1。

注意:0,n+1要当作>2k的点丢进栈里。

那么我们得到了一个最大的矩阵,当sum>k时,如何找到那个符合条件的子矩阵呢?

要用到分治的思想。

如果sum<=2k,那我们可以愉快地直接输出答案。

如果不,我们把矩阵看成若干行,从边上开始每次减去一行。

1.这一行的和<=k,那我们一直减,总会遇到k<=sum<=2k,因为值不可能一次跳过这个范围。

2.这一行的和>=k,那更好,子矩阵就在这单行里,我们枚举一下列即可得出答案。

这道题就解决了。

Code:

#include<cstdio>
#define ll long long
#define fo(i,x,y) for(ll i=x;i<=y;i++)
#define fd(i,x,y) for(ll i=x;i>=y;i--)
using namespace std;
const ll maxn=2005;
ll k,n,bzans,a[maxn][maxn],s[maxn][maxn],r[maxn][maxn],d[maxn];
ll sum(ll a,ll b,ll x,ll y) {
    return s[x][y]-s[x][b-1]-s[a-1][y]+s[a-1][b-1];
}

void solve2(ll a,ll b,ll x,ll y) {
    fo(e,b,y) {
        ll value=sum(a,b,a,e);
        if(value >= k && value <= 2*k) {
            printf("%lld %lld %lld %lld",a,b,a,e);
            bzans=1;
        }
        if(bzans) return;
    }   
}

void solve(ll a,ll b,ll x,ll y) {
    for(;sum(a,b,x,y) > 2*k && a!=x;) {
        if(sum(x,b,x,y) >= 2*k)
            solve2(x,b,x,y);
        if(bzans) return;
        x--;
    }
    if(a == x)
        solve2(a,b,x,y);
    if(bzans) return;

    printf("%lld %lld %lld %lld",a,b,x,y);
    bzans=1;
}

void pop(ll now,ll l) {
    for(;d[0] > 0 && r[d[d[0]]][l] > r[now][l];) {
        ll a=d[d[0]-1] + 1, b=l, x=now - 1, y=r[d[d[0]]][l] - 1;
        if(sum(a,b,x,y) >= k)
            solve(a,b,x,y);
        if(bzans) return;
        d[0]--;
    }
}

void insert(ll now,ll l) {
    pop(now,l);
    d[++d[0]]=now;
}

int main() {
    freopen("kup.in","r",stdin); freopen("kup.out","w",stdout);
    scanf("%lld %lld", &k, &n);
    fo(i,1,n)
        fo(j,1,n) {
            scanf("%lld", &a[i][j]);
            s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
        }
    fo(i,1,n) {
        r[i][n+1]=n+1;
        fd(j,n,1)
            if(a[i][j] > 2*k)
                r[i][j]=j; else r[i][j]=r[i][j+1];
    }

    fo(l,1,n) {
        d[0]=1; d[1]=0;
        fo(i,1,n) {
            insert(i,l);
            if(bzans) return 0;
        }
        insert(n+1,l);
        if(bzans) return 0;
    }
    printf("0 0 0 0");
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值