虽然题目提供了题解 ,但是作为菜鸡的我 表示看不懂 在超哥的帮助下,终于理解了,详细思路代码奉上
题解:
题目中只涉及两种位运算and和or,考虑把十进制数字转为二进制,分别计算每一位的贡献,原矩阵转化为01矩阵。对于and运算,只有全为1的子矩阵对答案有贡献;对于or运算,只有全为0的子矩阵没有贡献,答案即总子矩阵个数减去全0子矩阵个数。
-
把题目给的矩阵分解成若干个 01矩阵,从每个数二进制化的第一位开始(第一个矩阵),然后每个数二进制化的第二位(第二个矩阵),以此类推。假设这个矩阵是二进制下的第K 个矩阵,那么返回值就需要乘以 1<<k 即 tmp,and 累加 tmp * solve(1,i) or 累加 tmp * solve(0,i) 即为答案。
-
对于solve 函数 对于每一列 计算连续1 或者0 的数量 ,假设这一列 为 1,0,1,1
那么计算出的另一个矩阵的这一列为 1,0,1,2 ,如果是0 的话类似 -
然后取每一列计算矩形的贡献
-
对于calc() 函数 采用的单调栈的写法 详情 我的另一篇博客
代码:
由于账号问题 代码没法验证 仅供参考 样例过了就是对 哈哈
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int a[1010][1010]; //原始矩阵
int h[1010][1010]; //处理后的矩阵,每一列纵向连续个数
const int mod=1e9+7;
int n;
int top; //模拟栈 栈顶
int s[1010];
int r[1010]; //最右边下标
int l[1010]; //最左边下标
//对于矩阵每一行进行处理,查找矩阵的数量 即贡献
int calc(int *h)
{
//单调栈模拟
s[top=0]=0;
for(int i=1;i<=n;i++)
{
//注意 <= 号 下边是 < 这两个位置可以互换 不过必须 一个等于 一个不等于
while(top&&h[i]<=h[s[top]]) --top; //每次比较栈顶元素 h[i] 的大小
l[i]=s[top];s[++top]=i;
}
/*for(int i=1;i<=n;i++)
{
cout<<l[i]<<" ";
}
cout<<endl;*/
s[top=0]=n+1;
for(int i=n;i>=1;i--)
{
while(top&&h[i]<h[s[top]]) --top;
r[i]=s[top];
s[++top]=i;
}
/* for(int i=1;i<=n;i++)
{
cout<<r[i]<<" ";
}
cout<<endl;*/
int res=0;
for(int i=1;i<=n;i++)
{
res=(res+(i-l[i])*(r[i]-i)*h[i]%mod)%mod;
}
return res;
}
int solve(int v,int k)
{
//暴力 纵向连续数量
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if((a[i][j]>>k&1)==v)
{
h[i][j]=h[i-1][j]+1;
}
else
{
h[i][j]=0;
}
}
}
//遍历每一行
int res=0;
for(int i=1;i<=n;i++)
{
res=(res+calc(h[i])%mod)%mod;
}
return res;
}
int main()
{
// 单调栈测试
/* n=4;
int f[5]={0,1,2,1,4};
cout<<f[1]<<f[2]<<f[3]<<f[4]<<endl;
cout<<calc(f)<<endl;*/
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
scanf("%d",&a[i][j]);
}
}
int ans_and=0;
int ans_or=0;
//原始矩阵最终能组成多少子矩阵
int tot=n*(n+1)/2*n*(n+1)/2%mod;
for(int i=0;i<31;i++)
{
int tmp=1<<i;
ans_and=(ans_and+tmp*solve(1,i)%mod)%mod;
//总共的减去 零的 即为答案
ans_or=(ans_or+tmp*(tot-solve(0,i)%mod)%mod);
}
printf("%d %d\n",ans_and,ans_or);
}