洛谷 P5300 [GXOI/GZOI2019]与或和(单调栈)

问题 C: 与或和

时间限制: 3 Sec  内存限制: 512 MB
提交: 177  解决: 25
[提交] [状态] [命题人:admin]

题目描述

Freda 学习了位运算和矩阵以后,决定对这种简洁而优美的运算,以及蕴含深邃空间的结构进行更加深入的研究。

对于一个由非负整数构成的矩阵,她定义矩阵的AND值为矩阵中所有数二进制AND(&)的运算结果;定义矩阵的OR值为矩阵中所有数二进制OR(|)的运算结果。

给定一个N×N的矩阵,她希望求出:

该矩阵的所有子矩阵的AND值之和(所有子矩阵AND值相加的结果)。
该矩阵的所有子矩阵的OR值之和(所有子矩阵OR值相加的结果)。
接下来的剧情你应该已经猜到——Freda并不想花费时间解决如此简单的问题,所以这个问题就交给你了。

由于答案可能非常的大,你只需要输出答案对109+7取模后的结果。

 

输入

第一行是一个正整数N,表示矩阵的尺寸。
接下来N行,每行N个自然数,代表矩阵的一行。相邻两个自然数之间由一个或多个空格隔开。

 

输出

输出只有一行,包含两个用空格隔开的整数,第一个应为所有子矩阵AND值之和除以 109+7的余数,第二个应为所有子矩阵OR值之和除以109+7的余数。

 

样例输入

复制样例数据

3
1 0 0
0 0 0
0 0 0

样例输出

1 9

 

提示

该3×3矩阵共有9个1×1子矩阵、6个1×2子矩阵、6个2×1子矩阵4个2×2子矩阵、3个1×3子矩阵、3 个3×1子矩阵、2个2×3子矩阵、2个3×2子矩阵和1个3×3子矩阵。
只有一个子矩阵(仅由第一行第一列的那个元素构成的1×1矩阵)AND值为1,其余子矩阵的AND值均为0,总和为1。
包含第一行第一列那个元素的子矩阵有9个,它们的OR值为1,其余子矩阵的OR值为0 ,总和为9。

 

(题意:给你一个n*n的矩阵,求出所有子矩阵的按位与的和一级按位或的和。)

(思路:为了简化问题,我们可以这样想,我们可以考虑二进制,把每个数的二进制的某一位的贡献都算出来,再取和即可。计算第k位时,当按位与时,即求出元素全为1的子矩阵的个数,然后乘以(1<<k);按位或时,即求出全部子矩阵的个数减去全是0的子矩阵的个数,再乘以(1<<k)。首先,以(n,m)为右下角的子矩阵的个数为n*m。至于求全为1/0子矩阵的个数,我们就可以求出以所有点为右下角的全为1/0子矩阵的个数,加起来即可。至于怎么求,单调栈维护一个递增列的“高度”,每当出栈时就会减少一些贡献,减去即可。)

#include <iostream>
#include <cstdio>
#define ll long long
using namespace std;
const int N = 1e3+10;
const int mod = 1e9+7;
int a[N][N],h[N][N],sta[N],top;
ll f[N][N];
inline int read(){
    int x = 0; int zf = 1; char ch = ' ';
    while (ch != '-' && (ch < '0' || ch > '9')) ch = getchar();
    if (ch == '-') zf = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar(); return x * zf;
}
int n;
int main(void)
{

	n=read();
	ll ans1=0,ans2=0,tmp=0;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		{
			a[i][j]=read();	
		}
	//&
	for(int k=0;k<=30;k++)
	{
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=n;j++)	
			{
				h[i][j]=(a[i][j]&(1<<k))?h[i-1][j]+1:0;			
			}
			top=0;
			tmp=0;
			for(int j=1;j<=n;j++)
			{
				tmp+=h[i][j];
				while(top!=0&&h[i][sta[top]]>=h[i][j])
				{
					tmp-=(sta[top]-sta[top-1])*(h[i][sta[top]]-h[i][j]);
					top--; 
				}
				sta[++top]=j;
				ans1+=tmp<<k;
				ans1%=mod;				
			}

		}	
	}
	//|
	for(int k=0;k<=30;k++)
	{
		//cout<<"-------------"<<k<<endl;
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=n;j++)	
			{
				h[i][j]=(a[i][j]&(1<<k))?0:h[i-1][j]+1;		
				//cout<<h[i][j]<<" ";	
			}
			//cout<<endl;
			top=0;
			tmp=0;
			for(int j=1;j<=n;j++)
			{
				tmp+=h[i][j];
				while(top!=0&&h[i][sta[top]]>=h[i][j])
				{
					tmp-=(sta[top]-sta[top-1])*(h[i][sta[top]]-h[i][j]);
					top--;
				}
				sta[++top]=j;
				ans2+=(i*j-tmp)<<k;
				ans2%=mod;
			}
		}	
	}
	printf("%lld %lld\n",ans1,ans2);
	
} 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值