LOJ#3083.「GXOI / GZOI2019」与或和_单调栈_拆位

#3083. 「GXOI / GZOI2019」与或和

题目大意

给定一个\(N\times N\)的矩阵,求所有子矩阵的\(AND(\&)\)之和、\(OR(|)\)之和。

数据范围

\(1\le N\le 10^3\)\(val_{(i,j)} \le 2^{31}-1\)

题解

一眼题。

对于这种位运算的题,题都不用看完先想拆位,拆位可行那就拆,拆位不可行就不拆。

这里指的拆位可不可行具体指的是答案满不满足对于拆位之后的可加性。

发现这个题所求的是个和,那就果断拆开。

这样的话问题就变成了给定一个\(01\)矩阵求\(AND\)和(\(OR\)同理)。

发现只要是子矩阵中有\(0\)就是\(0\)

这种存在即可的式子最\(gay\)了。

绝大多数这种存在即可的式子都会依照“正难则反”变成“不存在即可”。

故此我们只需要求全\(1\)子矩阵个数。

这就很好求了,给定一个边长最多是\(1000\)的正方形\(01\)矩阵,问全\(1\)子矩阵个数。

每一行拿单调栈扫一扫就好了。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
#define N 1010 
int ori[N][N],a[N][N],bfr_up[N][N],bfr[N],aft[N],q[N],top;
int bin[31];
const int mod = 1000000007 ;
const int inv4 = 250000002 ;
char *p1,*p2,buf[1000000];
#define nc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++)
int rd() {int x=0; char c=nc(); while(c<48) c=nc(); while(c>47) x=(((x<<2)+x)<<1)+(c^48),c=nc(); return x;}
int main()
{
    bin[0]=1; for(int i=1;i<=30;i++) bin[i]=bin[i-1]<<1;
    int n=rd();
    for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) ori[i][j]=rd();
    int ans1=0,ans2=0;
    for(int bt=0;bt<=30;bt++)
    {
        int now1=0,now2=0;
        for(int i=1;i<=n;i++) for(int j=1;j<=n;j++)
        {
            a[i][j]=(ori[i][j]>>bt)&1;
            if(a[i][j]) bfr_up[i][j]=bfr_up[i-1][j]+1; else bfr_up[i][j]=0;
        }
        for(int i=1;i<=n;i++)
        {
            top=0;
            for(int j=n;j;j--)
            {
                while(top&&bfr_up[i][j]<bfr_up[i][q[top]]) bfr[q[top]]=j,top--;
                q[++top]=j;
            }
            while(top) bfr[q[top]]=0,top--;
            top=0;
            for(int j=1;j<=n;j++)
            {
                while(top&&bfr_up[i][j]<=bfr_up[i][q[top]]) aft[q[top]]=j,top--;
                q[++top]=j;
            }
            while(top) aft[q[top]]=n+1,top--;
            for(int j=1;j<=n;j++) (now1+=(ll)bfr_up[i][j]*(aft[j]-j)*(j-bfr[j])%mod)%=mod;
        }
        for(int i=1;i<=n;i++) for(int j=1;j<=n;j++)
        {
            a[i][j]^=1;
            if(a[i][j]) bfr_up[i][j]=bfr_up[i-1][j]+1; else bfr_up[i][j]=0;
        }
        for(int i=1;i<=n;i++)
        {
            top=0;
            for(int j=n;j;j--)
            {
                while(top&&bfr_up[i][j]<bfr_up[i][q[top]]) bfr[q[top]]=j,top--;
                q[++top]=j;
            }
            while(top) bfr[q[top]]=0,top--;
            top=0;
            for(int j=1;j<=n;j++)
            {
                while(top&&bfr_up[i][j]<=bfr_up[i][q[top]]) aft[q[top]]=j,top--;
                q[++top]=j;
            }
            while(top) aft[q[top]]=n+1,top--;
            for(int j=1;j<=n;j++) (now2+=(ll)bfr_up[i][j]*(aft[j]-j)*(j-bfr[j])%mod)%=mod;
        }
        now1=(ll)now1*bin[bt]%mod;
        now2=(ll)now2*bin[bt]%mod;
        (ans1+=now1)%=mod;
        (ans2+=((ll)n * n * (n + 1) * (n + 1) % mod * inv4 % mod * bin[bt] % mod-now2 + mod)%mod)%=mod;
    }
    printf("%d %d\n",ans1,ans2);
    return 0;
}

小结

模拟赛中被卡常了,\(LOJ\)也被卡常了。

这种取\(mod\)的题少取两次\(mod\)就好了。

posted @ 2019-04-23 18:57 JZYshuraK_彧 阅读(...) 评论(...) 编辑 收藏