题解-Decrease

[MdOI2020] Decrease

古老的博文。

今天巨佬团队 \(\texttt{luogu}\) 公开赛中的第三题,当时我写了好久才想到暴力做法 \(\texttt{42分}\),后来我还很离谱的写了个二维线段树,最终也没做出来。看来我还是太蒻了。

其实此题的做法是:简单差分

审题很重要,按照题目描述输入矩阵,题目中也说了,要快读:

for(int i=1,x,y,z;i<=m;i++){
    x=d(),y=d(),z=d();
    a[x][y]=z;
}

暴力做法:枚举覆盖正方形的左上角,暴力覆盖。 代码:

int main(){
    n=d(),m=d(),k=d();
    for(int i=1,x,y,z;i<=m;i++){
        x=d(),y=d(),z=d();
        a[x][y]=z;
    }
    for(int i=1;i<=n-k+1;i++)
        for(int j=1;j<=n-k+1;j++)
            if(a[i][j]!=0){
                ans+=abs(a[i][j]);
                int tmp=a[i][j];
                for(int x=i;x<=i+k-1;x++)
                    for(int y=j;y<=j+k-1;y++)
                        a[x][y]-=tmp;
            }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(a[i][j]!=0) return puts("-1"),0;
    printf("%lld\n",ans);
    return 0;
}

这么蒟蒻的做法,我拿到了 \(\texttt{42分}\),可见暴力的重要性。

然后我们发现,修改的区间一定是一个矩形,而且是增减修改,并且是统一修改,就应该想到用差分。把每行单独拎出来差分一下,形成差分数组 \(cf[][]\)

for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
        cf[i][j]=a[i][j]-a[i][j-1];

然后暴力枚举覆盖正方形左上角端点,如果不为零就把整个右下角的 \(k\times k\) 矩阵减去左上角端点的数,然后让 \(\texttt{ans}\) 加上左上角端点数的绝对值。因为当枚举到一个端点的时候,它同一行左端的端点肯定被清零了,所以到这个端点时这个端点的值就是 \(cf[][]\) 了。代码:

for(int i=1;i<=n-k+1;i++)
    for(int j=1,num=0;j<=n-k+1;j++){
        num=cf[i][j];
        if(num!=0){
            ans+=abs(num);
            for(int t=i;t<=i+k-1;t++)
                cf[t][j]-=num,cf[t][j+k]+=num; //k*k矩阵修改通过差分优化,时间复杂度为O(n)
        }
    }

因为要覆盖的左端点少,所以这样的代码时间复杂度是合理的。

题目中说还有“无法使矩阵中所有数都变为 \(0\)”的情况,所以最后再 \(n\times n\) 暴力扫一遍差分矩阵,如果还有没清零的数,就 \(\texttt{puts("-1")}\)。代码:

for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
        if(cf[i][j]) return puts("-1"),0;

就这么简单,普及难度,可是我比赛时缺没想到。如果你懂了,那么蒟蒻就放代码了:

#include <bits/stdc++.h>
using namespace std;
#define lng long long
namespace rd{
    const int L=1<<16;
    char buf[L],*S,*T;
    inline char Gc_(){
        if(S==T){T=(S=buf)+fread(buf,1,L,stdin);
            if(S==T) return EOF;}
        return *S++;
    }
    inline char Gc(){return getchar();}
    inline int d(){
        char c;int f=1,x;
        for(c=Gc();c>'9'||c<'0';c=Gc())
            if(c=='-') f=-1;
        for(x=0;c>='0'&&c<='9';c=Gc())
            x=(x<<1)+(x<<3)+c-'0';
        return x*f;
    }
}using namespace rd;
const int N=5e3+10;
int n,m,k,a[N][N];
lng ans;
int cf[N][N];
int main(){
    n=d(),m=d(),k=d();
    for(int i=1,x,y,z;i<=m;i++){
        x=d(),y=d(),z=d();
        a[x][y]=z;
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            cf[i][j]=a[i][j]-a[i][j-1];
    for(int i=1;i<=n-k+1;i++)
        for(int j=1,num=0;j<=n-k+1;j++){
            num=cf[i][j];
            if(num!=0){
                ans+=abs(num);
                for(int t=i;t<=i+k-1;t++)
                    cf[t][j]-=num,cf[t][j+k]+=num;
            }
        }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(cf[i][j]) return puts("-1"),0;
    printf("%lld\n",ans);
    return 0;
}

祝大家学习愉快!

猜你喜欢

转载自www.cnblogs.com/Wendigo/p/12444590.html