【題解】【原創題目】薇爾莉特


【題解】【原創題目】薇爾莉特

出題人:辰星凌

驗題人:無

題目傳送門:薇爾莉特

【題目描述】

給出一個 \(n\)\(m\) 列的矩陣(最初全為 \(0\)),有 \(T\) 個操作,每次操作選出一個子矩陣,將其中的所有元素都對 \(x\) 進行一次 \(or\),求最后的矩陣。

【分析】

【Solution #1】

純暴力,沒有坑點,按照題意模擬一下就可以了。

時間復雜度:\(O(Tn^2)\)

分數:\(20pt\)

【Code #1】
#include<algorithm>
#include<iostream>
#include<cstdio>
#define Re register int
#define LL long long
using namespace std;
const int N=503,P=1e9+7;
int n,m,a,b,c,d,x,T,HYJ,Ans1,Ans2,A[N][N];
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
int main(){
//  freopen("123.txt","r",stdin);
    in(n),in(m),in(T),in(HYJ),Ans2=HYJ;
    while(T--){
        in(a),in(b),in(c),in(d),in(x);
        for(Re i=a;i<=c;++i)
            for(Re j=b;j<=d;++j)
                A[i][j]|=x;
    }
    for(Re i=1;i<=n;++i)
        for(Re j=1;j<=m;++j)
            (Ans1+=A[i][j])%=P,Ans2^=A[i][j];
    printf("%d %d %d\n",Ans1,Ans2,(LL)Ans1*Ans2%P);
}

【Solution #2】

如果把取“或”換成“異或”就是一個區修單查的二維樹狀數組板子,可惜 \(or\) 不滿足可減性。

那如果如果它分為 \(log\) 個二進制位呢?

對於任意一個二進制位,都是滿足可減性的,這下子可以放心地上 \(\text{BIT}\) 了。

直接開 \(log\) 棵二維 \(\text{BIT}\),分別單獨處理所有的二進制位,每次操作暴力拆分 \(x\) 進行 \(log\) 次區間修改,最后再對矩陣中所有元素的每一位進行單點查詢即可。

時間復雜度:\(O(kTlog^2n+klog^2n)\),其中 \(k\) 最大為 \(30\)

注:要卡下常才能過 \(Subtask\ 2\)

分數:\(60pt\)

【Code #2】

(此代碼並非完美,還有至少三處優化,但過 \(Subtask\ 2\) 足矣)

#include<algorithm>
#include<iostream>
#include<cstdio>
#define Re register int
#define LL long long
using namespace std;
const int N=503,P=1e9+7;
int n,m,a,b,c,d,x,T,HYJ,Ans1,Ans2,A[N][N];
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
struct QAQ{//二維樹狀數組
    int C[N][N];
    inline void add_(Re x,Re y,Re z){while(x<=n){Re i=y;while(i<=m)C[x][i]+=z,i+=i&(-i);x+=x&(-x);}}
    inline void change(Re x1,Re y1,Re x2,Re y2){//子矩陣修改
        add_(x1,y1,1);
        add_(x1,y2+1,-1);
        add_(x2+1,y1,-1);
        add_(x2+1,y2+1,1);
    }
    inline int ask(Re x,Re y){//單點查詢
        Re ans=0;
        while(x){Re i=y;while(i)ans+=C[x][i],i-=i&(-i);x-=x&(-x);}
        return ans>0;//只要有一次1即可
    }
}TT[31];
int main(){
//  freopen("123.txt","r",stdin);
    in(n),in(m),in(T),in(HYJ),Ans2=HYJ;
    while(T--){
        in(a),in(b),in(c),in(d),in(x);
        for(Re p=0;p<=30;++p)if((x>>p)&1)TT[p].change(a,b,c,d);//分解inf,只有為1的位才調用BIT
    }
    for(Re p=0;p<=30;++p)
        for(Re i=1;i<=n;++i)
            for(Re j=1;j<=m;++j)
                if(TT[p].ask(i,j))A[i][j]|=(1<<p);//獲取最終的A數組
    for(Re i=1;i<=n;++i)
        for(Re j=1;j<=m;++j)
            (Ans1+=A[i][j])%=P,Ans2^=A[i][j];
    printf("%d %d %d\n",Ans1,Ans2,(LL)Ans1*Ans2%P);
}

【Solution #3】

考慮如何優化 \(Solution\ \#2\)

依舊是對於每個二進制位單獨處理。

想想“或”還有沒有什么其他的性質?

單次覆蓋性!(其實就是自己瞎取的一個名字)

現在只看一個元素,只要有一次操作中對 \(1\) 取了 \(or\),那么以后不管和誰 \(or\),都依然是 \(1\)。換句話說,將操作中 \(x\)\(1\) 的二進制位視作一張大小為 \((x_2-x_1)*(y_2-y_1)\) 的布,無論有多少張布覆蓋了這個元素,且無論覆蓋了多少次,只要有其中一次蓋住了它,那么它最終就一定屬於被蓋住的部分(即對應的二進制位為 \(1\))。

因此,如果我們在一次操作中覆蓋了某個元素,那么下一次枚舉到這里時就可以直接跳過它(或者說直接刪掉該元素),易知每一個元素都只會被覆蓋一次,因此對於一個二進制位下的矩陣覆蓋總復雜度為 \(O(n^2)\)

其中“刪除元素”可以用並查集實現:對於每一行都開一個並查集表示該行中所有列的覆蓋情況,當 \((i,j)\) 被覆蓋后,把 \((i,j)\)\((i,j+1)\) 所在的聯通塊連起來,然后再枚舉該行下一個聯通塊(即 \((i,j+1)\) 所在連通塊),大致代碼如下:

/*從x1到x2枚舉i*/
for(Re j=find(y1);j<=y2;j=find(j+1))
    (i,j)=1,merge(j,j+1);

當然,這樣子做只優化了對於列的枚舉,行的枚舉同理,稍作修改即可。

時間復雜度:\(O(kT+n^2\alpha^2(n)+kn^2)\),其中 \(k\) 最大為 \(30\)(一個不需要卡常也能輕松 \(\text{AC}\) 的優秀算法)。

分數:\(100pt\)

【Code #3】
#include<algorithm>
#include<iostream>
#include<cstdio>
#define Re register int
#define LL long long
using namespace std;
const int N=503,P=1e9+7;
int n,m,a,b,c,d,x,T,HYJ,Ans1,Ans2,A[N][N];
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
struct QAQ{
    bool B[N][N];
    struct QWQ{
        int fa[N];
        inline void CL(){for(Re i=0;i<=m+1;++i)fa[i]=i;}//注意初始化要枚舉到n+1
        inline int find(Re x){return x==fa[x]?x:fa[x]=find(fa[x]);}
    }f[N];
    inline void CL(){
        for(Re i=1;i<=n;++i)f[i].CL();//第i(1<=i<=n)個f表示第i行中各列的覆蓋情況
        for(Re i=1;i<=n+1;++i)f[0].fa[i]=i;//用第0個f表示行的覆蓋情況
    }
    inline void change(Re x1,Re y1,Re x2,Re y2){
        for(Re i=f[0].find(x1);i<=x2;i=f[0].find(i+1)){
            for(Re j=f[i].find(y1);j<=y2;j=f[i].find(j+1))
                B[i][j]=1,f[i].fa[j]=f[i].find(j+1);//第i行第j個被覆蓋,去掉它(放到j+1腳下)
            if(f[i].find(1)==m+1)f[0].fa[i]=f[0].find(i+1);
            //如果第i行全部被覆蓋(即1到m全部都放到了m+1腳下),則可以將第i行去掉了(放到第i+1行腳下)
        }
    }
}TT[31];
int main(){
//  freopen("123.txt","r",stdin);
    in(n),in(m),in(T),in(HYJ),Ans2=HYJ;
    for(Re p=0;p<=30;++p)TT[p].CL();//初始化
    while(T--){
        in(a),in(b),in(c),in(d),in(x);
        for(Re p=0;p<=30;++p)if((x>>p)&1)TT[p].change(a,b,c,d);//分解inf
    }
    for(Re p=0;p<=30;++p)
        for(Re i=1;i<=n;++i)
            for(Re j=1;j<=m;++j)
                if(TT[p].B[i][j])A[i][j]|=(1<<p);//獲取最終的A數組
    for(Re i=1;i<=n;++i)
        for(Re j=1;j<=m;++j)
            (Ans1+=A[i][j])%=P,Ans2^=A[i][j];
    printf("%d %d %d\n",Ans1,Ans2,(LL)Ans1*Ans2%P);
}

【題外話】

本題實際上是兩個知識點的糅合:
\((1).\) 按照二進制位拆分單獨處理(見 【題解】\(\text{GXOI/GZOI2019}\) 機房游記 \(\text{Day1T1}\)
\((2).\) 冰茶姬優化矩陣覆蓋的枚舉(見 【雜文】隨心一記 小知識第二條)

\(Code\ \#2\) 最初第 \(4\) 個點死活卡不過去,最后將結構體套結構體換成一個單一的結構體就過了(沒想到結構體的調用對常數影響這么大)。

加上樣例一共 \(12\) 個不同的 \(HYJ\)\(12\) 個數字 \(11\) 個梗您知道幾個?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM