题解 P9120【[春季测试 2023] 密码锁】

$\text{Link}$

题意

有一个 $k\times n$ 的矩阵,对于每一列,你可以任意旋转,求每行的极差的最大值的最小值。多组数据。

$T\ge 1$,$1\le a_{i,j}\le3\times10^4$,$1\le k\le 4$。

对于 $1\le k\le 3$,$1\le n\le 5\times 10^4$,$\sum n\le 1.5\times 10^5$。

对于 $k=4$,$1\le n\le 10^4$,$\sum n\le 3\times 10^4$。

思路

对每个 $k$ 处理。

$k=1$

直接求极差,时间复杂度 $O(\sum nk)$。

$k=2$

考虑将小的数放上面,大的数放下面。

思考一下这样为什么是对的?考虑全局最小值和全局最大值所在的行,显然放在两行不劣于放在同一行,设最小值放上面,最大值放下面,那么要想让上面的数尽量小,下面的数尽量大,就是上面的方法。

时间复杂度 $O(\sum nk)$。

namespace Sub12{
    const int N=5e4+10;
    int n,a[3][N];
    inline void main(){
        while(T--){
            n=read();
            for(int i=1;i<=k;i++)
                for(int j=1;j<=n;j++)
                    a[i][j]=read();
            if(k==2){
                for(int i=1;i<=n;i++)
                    if(a[1][i]>a[2][i])
                        swap(a[1][i],a[2][i]);
            }
            int ans=0;
            for(int i=1;i<=k;i++){
                int mn=3e4+10,mx=0;
                for(int j=1;j<=n;j++)
                    mn=min(mn,a[i][j]),mx=max(mx,a[i][j]);
                ans=max(ans,mx-mn);
            }
            write(ans),putc('\n');
        }
        flush();
    }
}

$k=3$

让最大值最小,可以先考虑二分答案,检验答案 $x$ 是否可行。

考虑沿用上面的想法,先固定全局最小值在第一行,枚举全局最大值在第几行。

对于第 $i$ 列,我们枚举转了几次,判断是否满足最小值和最大值所在两行的限制(即在那一行上的数和那一行的最小值/最大值相差超不超过 $x$),如果满足限制,我们设剩下一行上的数为 $a$。

假设有一条数轴,我们可以在 $a$ 位置放上一个颜色为 $i$ 的点(每个颜色的点显然只有 $O(k)$ 个),我们要判断剩下一行的极差是否可以不超过 $x$,就是问是否有一条长度为 $x$ 的线段覆盖了所有 $n$ 种颜色。

我们维护 $[mn,mx]$ 内每个点作为左端点时,可以覆盖几种颜色,对于每种颜色,对于左端点 $l$,如果 $[l,l+x]$ 内有这个颜色的点就可以让这个左端点加一,我们考虑对每个这个颜色的点 $a$ 将 $[a-x,a]$ 内的左端点加一。

注意到不能重复加,对于每个颜色,我们开个 set 存这个颜色已经加入了的点,设要加入 $a$,$a$ 在集合内的前驱后继分别为 $p,q$,则需要将 $[\max(p+1,a-x),\min(a,q-x-1)]$ 加一。注意左端点大于右端点的时候。

差分即可,判断最大值是否等于 $n$,注意到不能给单次复杂度带上 $v$,需要维护有哪些位置被加了。

用并查集加上标记清空可做到时间复杂度为 $O(\sum nk^2\log v)$。

实现的代码由于排序有二 $\log$。

namespace Sub3{
    const int N=5e4+10,M=3e4+10;
    int n,a[3][N],mn,mx;
    struct Delta{
        struct File{
            int p,v;
            inline friend bool operator<(const File &a,const File &b){
                return a.p<b.p;
            }
        }f[M];
        int f[M],top,nxt[],g[M];
        inline void clear(){
            for(int i=1;i<=top;i++)
                g[f[i].p]=0;
            top=0;
        }
        inline void add(int p,int v){
            if(!g[p]) g[p]=++top,f[top].p=p,f[top].v=0;
            f[g[p]].v+=v;
        }
        inline void modify(int l,int r,int v){
            if(l>r) return ;
            add(r+1,-v),add(l,v);
        }
        inline int query(){
            int mx=0;
            sort(f+1,f+top+1);
            for(int i=1,s=0;i<=top;i++){
                s+=f[i].v;
                mx=max(mx,s);
            }
            return mx;
        }
    }tr;
    struct node{
        int c,x;
    };
    multiset<int>ip[N];
    vector<node>p;
    inline int pre(int col,int x){
        auto tmp=ip[col].upper_bound(x);
        if(tmp==ip[col].begin()) return 0;
        return *(--tmp);
    }
    inline int nxt(int col,int x){
        auto tmp=ip[col].upper_bound(x);
        if(tmp==ip[col].end()) return 3e5;
        return *tmp;
    }
    inline void add(int col,int x,int mid,int v){
        int pr=pre(col,x),nx=nxt(col,x);
        if(pr==x) return ;
        tr.modify(max({pr+1,x-mid,mn}),min(x,max(0,nx-mid-1)),v);
        ip[col].insert(x);
    }
    inline void cl(int id){
        if(id==0){
            vector<node>op;
            swap(p,op);
        }else{
            multiset<int>op;
            swap(ip[id],op);
        }
    }
    inline bool check(int mid){
        cl(0);
        tr.clear();
        for(int i=1;i<=n;i++){
            cl(i);
            for(int j=0;j<=2;j++){
                int x=a[(0+j)%3][i],y=a[(1+j)%3][i],
                    z=a[(2+j)%3][i];
                if(x-mn<=mid&&mx-y<=mid)
                    p.push_back((node){i,z});
            }
        }
        for(auto tmp:p){
            int col=tmp.c,x=tmp.x;
            add(col,x,mid,1);
        }
        int tmp=tr.query();
        if(tmp==n) return 1;
        cl(0);
        tr.clear();
        for(int i=1;i<=n;i++){
            cl(i);
            for(int j=0;j<=2;j++){
                int x=a[(0+j)%3][i],y=a[(1+j)%3][i],
                    z=a[(2+j)%3][i];
                if(mx-x<=mid&&y-mn<=mid)
                    p.push_back((node){i,z});
            }
        }
        for(auto tmp:p){
            int col=tmp.c,x=tmp.x;
            add(col,x,mid,1);
        }
        tmp=tr.query();
        return tmp==n;
    }
    inline void main(){
        while(T--){
            n=read();
            mn=3e4,mx=0;
            for(int i=0;i<k;i++)
                for(int j=1;j<=n;j++)
                    a[i][j]=read(),
                    mn=min(a[i][j],mn),
                    mx=max(a[i][j],mx);
            int l=0,r=mx-mn,ans=3e4;
            while(l<=r){
                int mid=l+r>>1;
                if(check(mid)) r=mid-1,ans=mid;
                else l=mid+1;
            }
            write(ans),putc('\n');
        }
        flush();
    }
}

$k=4$

沿用上面的想法,设二分到 $x$,先固定全局最小值在第一行,枚举全局最大值在第几行。

对于第 $i$ 列,我们枚举转了几次,判断是否满足最小值和最大值所在两行的限制,如果满足限制,我们设剩下两行上的数为 $a,b$。我们在平面上 $(a,b)$ 位置放一个颜色为 $i$ 的点。判断是否有一个 $x\times x$ 的矩形能覆盖 $n$ 种颜色。

对于每一行维护有哪些点。考虑枚举矩形的下边界 $d$,上边界就是 $d-x$,考虑下移下边界,需要删掉 $d-x$ 行的所有点,加入 $d+1$ 行的所有点。删除方法和加入方法类似。

同样维护左边界 $x$ 坐标为 $l$ 的时候能覆盖几种颜色,需要动态修改,查询全局最大值,可以用线段树维护。

实现上,我们不能带上 $v$ 的复杂度,需要维护有哪些行有点,当前上下范围内有哪些行有点,可以用队列维护。注意清空只能清空有点的行,线段树也不能整棵树搜索清空,要一步步撤销操作。具体实现可以参考以下代码。

时间复杂度 $O(\sum nk^2\log^2v)$。

namespace Sub4{
    const int N=1e4+10,M=3e4+10;
    int n,a[4][N],mn,mx;
    struct Segment_Tree{
        #define ls (rt<<1)
        #define rs (rt<<1|1)
        int tag[M<<2],mx[M<<2];
        inline void pushup(int rt){
            mx[rt]=max(mx[ls],mx[rs]);
        }
        inline void pushadd(int rt,int v){
            tag[rt]+=v,mx[rt]+=v; 
        }
        inline void pushdown(int rt){
            if(tag[rt]){
                pushadd(ls,tag[rt]);
                pushadd(rs,tag[rt]);
                tag[rt]=0;
            }
        }
        inline void modify(int rt,int l,int r,int L,int R,int v){
            if(L>R) return ;
            if(L<=l&&r<=R){
                pushadd(rt,v);
                return ;
            }
            pushdown(rt);
            int mid=l+r>>1;
            if(L<=mid) modify(ls,l,mid,L,R,v);
            if(R>mid) modify(rs,mid+1,r,L,R,v);
            pushup(rt);
        }
        inline int query(){
            return mx[1];
        }
    }tr;
    struct node{
        int c,x;
    };
    multiset<int>ip[N];
    vector<node>p[M];
    inline int pre(int col,int x){
        auto tmp=ip[col].upper_bound(x);
        if(tmp==ip[col].begin()) return 0;
        return *(--tmp);
    }
    inline int nxt(int col,int x){
        auto tmp=ip[col].upper_bound(x);
        if(tmp==ip[col].end()) return 3e5;
        return *tmp;
    }
    struct DEL{
        int l,r,v;
    };
    vector<DEL>del;
    set<int>mdp,mbp;
    inline void add(int col,int x,int mid,int v){
        if(v==-1) ip[col].erase(ip[col].find(x));
        int pr=pre(col,x),nx=nxt(col,x);
        tr.modify(1,mn,mx,max({pr+1,x-mid,mn}),min(x,max(0,nx-mid-1)),v);
        del.push_back((DEL){max({pr+1,x-mid,mn}),min(x,max(0,nx-mid-1)),-v});
        if(v==1) ip[col].insert(x);
    }
    inline void cl(int id){
        multiset<int>op;
        swap(ip[id],op);
    }
    inline void CLEAR(){
        for(auto tmp:del)
            tr.modify(1,mn,mx,tmp.l,tmp.r,tmp.v);
        vector<DEL>det;
        swap(del,det);
        for(auto tmp:mdp){
            vector<node>mdq;
            swap(p[tmp],mdq);
        }
        set<int>mdq;
        swap(mdp,mdq);
        set<int>mbq;
        swap(mbp,mbq);
    }
    inline int beg(){
        if(mbp.empty()) return 1e9;
        return *mbp.begin();
    }
    inline void dbeg(){
        mbp.erase(mbp.begin());
    }
    inline int nxt(int x){
        auto tmp=mdp.upper_bound(x);
        if(tmp==mdp.end()) return 3e5;
        return *tmp;
    }
    inline bool check(int A,int B,int mid){
        for(int i=1;i<=n;i++){
            cl(i);
            for(int j=0;j<=3;j++){
                int x=a[(0+j)%4][i],y=a[(1+j)%4][i],
                    z=a[(2+j)%4][i],f=a[(3+j)%4][i];
                if(A) swap(y,z);
                if(B) swap(x,y);
                if(x-mn<=mid&&mx-y<=mid)
                    p[z].push_back((node){i,f}),mdp.insert(z);
            }
        }
        mbp=mdp;
        for(int i=beg();i<=mid;i=nxt(i)){
            for(auto tmp:p[i]){
                int col=tmp.c,x=tmp.x;
                add(col,x,mid,1);
            }
        }
        int tmp=tr.query();
        if(tmp==n) return CLEAR(),1;
        for(int i=nxt(mid);i<=mx;i=nxt(i)){
            for(auto tmp:p[i]){
                int col=tmp.c,x=tmp.x;
                add(col,x,mid,1);
            }
            for(int id=beg();id<=i-mid-1;id=beg()){
                for(auto tmp:p[id]){
                    int col=tmp.c,x=tmp.x;
                    add(col,x,mid,-1);
                }
                dbeg();
            }
            int tmp=tr.query();
            if(tmp==n) return CLEAR(),1;
        }
        return CLEAR(),0;
    }
    inline bool check(int mid){
        int tmp=check(0,0,mid);
        if(tmp) return 1;
        tmp=check(0,1,mid);
        if(tmp) return 1;
        tmp=check(1,0,mid);
        if(tmp) return 1;
        return 0;
    }
    inline void main(){
        while(T--){
            n=read();
            mn=3e4,mx=0;
            for(int i=0;i<k;i++)
                for(int j=1;j<=n;j++)
                    a[i][j]=read(),
                    mn=min(a[i][j],mn),
                    mx=max(a[i][j],mx);
            int l=0,r=mx-mn,ans=3e4;
            while(l<=r){
                int mid=l+r>>1;
                if(check(mid)) r=mid-1,ans=mid;
                else l=mid+1;
            }
            write(ans),putc('\n');
        }
        flush();
    }
}

提交记录(7.39KB)

posted @ 2023-03-14 13:10  ffffyc  阅读(22)  评论(0)    收藏  举报  来源