[GXOI/GZOI2019]与或和[单调栈]

也许更好的阅读体验

D e s c r i p t i o n \mathcal{Description} Description

给出一个 n × n n\times n n×n 的, 元素为自然数的矩阵.
这个矩阵有许许多多个子矩阵, 定义它的所有子矩阵形成的集合为 S S S .
对于一个矩阵 k k k , 定义 f ( k ) f (k) f(k) k k k 中所有元素的 A N D AND AND 值 (按位与).
对于一个矩阵 k k k , 定义 g ( k ) g(k) g(k) k k k 中所有元素的 O R OR OR 值 (按位或).
请求出所有子矩阵的 f ( k ) f (k) f(k) 之和与所有子矩阵的 g ( k ) g(k) g(k) 之和, 即 ∑ k ∈ S f ( k ) \begin{aligned}\sum_{k\in S} f (k)\end{aligned} kSf(k) ∑ k ∈ S g ( k ) \begin{aligned}\sum_{k\in S} g (k)\end{aligned} kSg(k)
由于答案可能很大, 只需要输出答案对 1 e 9 + 7 1e9+7 1e9+7取模的结果.

n ≤ 1000 , n\leq1000, n1000,矩阵中每个元素大小在 i n t int int范围内

S o l u t i o n \mathcal{Solution} Solution

对于有位运算操作的题目,计算贡献时首先考虑的方法就是按位计算贡献
因为某一位的贡献是不会受到其他位影响的,而整体计算因为位运算的特殊性,还是会考虑到按位计算

我们枚举每一位 k k k,然后把矩阵换成 0 / 1 0/1 0/1矩阵,其中 0 0 0元素表示原本矩阵中这一元素在枚举到的这位二进制表示下为 0 0 0,为 1 1 1则为 1 1 1
考虑这个 0 / 1 0/1 0/1矩阵中有多少个子矩阵会被计算贡献,最后把这个数乘以 2 k 2^k 2k

对于与运算,就是要求里面有多少个全 1 1 1矩阵
对于或运算,就是要求里面有多少个有 1 1 1矩阵

与运算和或运算实际上可以互相转换
1 1 1矩阵数=所有矩阵数-有 0 0 0矩阵数
1 1 1矩阵数=所有矩阵数-全 0 0 0矩阵数

a l l all all n × n n\times n n×n的矩阵所包含的所有子矩阵
显然 a l l = ( n ( n + 1 ) 2 ) 2 all=(\frac{n(n+1)}{2})^2 all=(2n(n+1))2

之后考虑怎么计算满足条件的矩阵个数

55 55 55分解法 O ( l o g 2 n ⋅ n 3 ) O(log_2n\cdot n^3) O(log2nn3)
考虑枚举矩阵的上边界,下边界
我们计算在这样的矩阵中满足要求的个数
1 1 1矩阵显然容易做些
考虑枚举以一个矩阵中最左边的 1 1 1出现位置
这样可以不重不漏全部计算
显然,只要包含了这个有 1 1 1的一列,当前范围内所有矩阵都是合法的
注意不要和上一个有 1 1 1的那列重复了

s u m [ i ] [ j ] sum[i][j] sum[i][j]表示第 j j j列到第 i i i个位置共有多少 1 1 1
只要 s u m [ 下 边 界 ] [ 当 前 列 ] − s u m [ 上 边 界 − 1 ] [ 当 前 列 ] > 0 sum[下边界][当前列]-sum[上边界-1][当前列]>0 sum[][]sum[1][]>0就说明这一列有 1 1 1
其他的很简单,具体的过会儿看代码吧

100 100 100分解法 O ( l o g 2 n ⋅ n 2 ) O(log_2n\cdot n^2) O(log2nn2)

考虑枚举矩阵的下边界
计算全 1 1 1矩阵
f [ i ] [ j ] f[i][j] f[i][j]表示第 j j j列,从 i i i开始往上有多少个连续的 1 1 1
然后枚举到哪一列了,用 c n t cnt cnt表示每次会增加多少个矩阵
用单调栈维护一下即可

C o d e \mathcal{Code} Code

55 55 55
试图卡常碰运气版

/*******************************
Author:Morning_Glory
LANG:C++
Created Time:2019年09月10日 星期二 14时17分44秒
*******************************/
#include <cstdio>
#include <fstream>
#include <algorithm>
#define rint register int
using namespace std;
const int maxn = 1003;
const int mod = 1000000007;
//{{{cin
struct IO{
	template<typename T>
	IO & operator>>(T&res){
		res=0;
		bool flag=false;
		char ch;
		while((ch=getchar())>'9'||ch<'0')	flag|=ch=='-';
		while(ch>='0'&&ch<='9')	res=(res<<1)+(res<<3)+(ch^'0'),ch=getchar();
		if (flag)	res=~res+1;
		return *this;
	}
}cin;
//}}}
int n,mx,all,ans1,ans2;
int lt[maxn],up[maxn];
int a[maxn][maxn],sum[maxn][maxn];
bool hav[maxn];
bool b[maxn][maxn];
//{{{calc
inline int calc (int mi,bool opt)
{
	int res=0;
	for (rint i=1;i<=n;++i)
		for (int j=1;j<=n;++j){
			b[i][j]=(a[i][j]&mi);
			b[i][j]^=opt;
			sum[i][j]=sum[i-1][j]+b[i][j];
		}
	for (rint i=1;i<=n;++i)//上边界
		for (rint j=i;j<=n;++j){//下边界
			int last=0;
			for (rint k=1;k<=n;++k)//第一个1
				if (sum[j][k]-sum[i-1][k])	res=(res+(k-last)*(n-k+1))%mod,last=k;
		}
	return res;
}
//}}}
int main()
{
	cin>>n;
	for (rint i=1;i<=n;++i)
		for (rint j=1;j<=n;++j)
			cin>>a[i][j],mx=max(mx,a[i][j]);
	all=n*(n+1)/2%mod;
	all=1ll*all*all%mod;
	for (rint k=0;k<=31;++k){
		int t=1<<k;
		if (t>mx)	break;
		int num=calc(t,1);
		num=(all-num+mod)%mod;
		ans1=(ans1+1ll*num*t%mod)%mod;
		num=calc(1<<k,0);
		ans2=(ans2+1ll*num*t%mod)%mod;
	}
	printf("%d %d\n",ans1,ans2);
	return 0;
}

100 100 100

/*******************************
Author:Morning_Glory
LANG:C++
Created Time:2019年09月10日 星期二 14时17分44秒
*******************************/
#include <cstdio>
#include <fstream>
#include <algorithm>
using namespace std;
const int maxn = 1003;
const int mod = 1000000007;
//{{{cin
struct IO{
	template<typename T>
	IO & operator>>(T&res){
		res=0;
		bool flag=false;
		char ch;
		while((ch=getchar())>'9'||ch<'0')	flag|=ch=='-';
		while(ch>='0'&&ch<='9')	res=(res<<1)+(res<<3)+(ch^'0'),ch=getchar();
		if (flag)	res=~res+1;
		return *this;
	}
}cin;
//}}}
int n,mx,all,ans1,ans2;
int stk[maxn];
int a[maxn][maxn],f[maxn][maxn];
bool b[maxn][maxn];
//{{{calc
inline int calc (int mi,bool opt)
{
	int res=0;
	for (int i=1;i<=n;++i)
		for (int j=1;j<=n;++j){
			b[i][j]=(a[i][j]&mi);
			b[i][j]^=opt;
			f[i][j]=b[i][j]?f[i-1][j]+1:0;
		}
	for (int i=1;i<=n;++i){
		int cnt=0,top=0;
		for (int j=1;j<=n;++j){
			cnt+=f[i][j];
			while (top&&f[i][stk[top]]>f[i][j])	cnt-=(stk[top]-stk[top-1])*(f[i][stk[top]]-f[i][j]),--top;
			res=(res+cnt)%mod;
			stk[++top]=j;
		}
	}
	return res;
}
//}}}
int main()
{
	cin>>n;
	for (int i=1;i<=n;++i)
		for (int j=1;j<=n;++j)
			cin>>a[i][j],mx=max(mx,a[i][j]);
	all=n*(n+1)/2%mod;
	all=1ll*all*all%mod;
	for (int k=0;k<=31;++k){
		int t=1<<k;
		if (t>mx)	break;
		int num=calc(t,0);
		ans1=(ans1+1ll*num*t%mod)%mod;
		num=calc(1<<k,1);
		num=(all-num+mod)%mod;
		ans2=(ans2+1ll*num*t%mod)%mod;
	}
	printf("%d %d\n",ans1,ans2);
	return 0;
}

如有哪里讲得不是很明白或是有错误,欢迎指正
如您喜欢的话不妨点个赞收藏一下吧

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值