BZOJ 4750 单调栈+异或性质

链接:https://www.lydsy.com/JudgeOnline/problem.php?id=4750

题意:长度为N的序列,求

 

  首先对于这种异或求和的问题,一般都是按位拆分然后分别计算贡献。

所以上式子可以化简为\sum _{L=1}^{N}\sum _{R=L}^{N}max(L,R)\sum _{k=0}^{30}b[L,R,k]*2^{k}     

(b【k】指L~R所有元素二进制分解后第K位的异或和)。

两个N的for循环显然会超时,所以想办法优化,我们不妨先将K的枚举提到最前面。

\sum _{k=0}^{30}2^{k}*\sum _{L=1}^{N}\sum _{R=L}^{N}max(L,R)*b[L,R,k]

先不考虑异或和的问题,式子的后半部分显然是求一个区间的最大值,首先想到单调队列,O(N)遍历序列得到

当前子串的最大值,但是单调队列并不适合求整个序列中具体到每一个字串的最大值。,,,,总之,这道题应该

用单调栈去求第i个数作为最大值所对应的最大区间。然后计算A中每个元素所产生的贡献。

然后,我们的表达式可以变成:

\sum _{k=0}^{30}2^{k}*\sum_{i=1}^{N}\sum_{j=1}^{tmp}1*xor(tmp)  (tmp表示a【i】对应的区间数目,xor表示对应区间的异或和)。

显然tmp相关的求和式要么等于0,要么等于1,不用管这个,总之现在的问题变为给一个区间(L,R),

区间元素为A数组对应二进制位的第K位,A中最大值的对应坐标为index,问有多少个包含index的异或

和等于1的子串。

这里就需要用到异或的性质,首先我们知道:S2^..S6^S7==S7^S1。(S是递推过去的异或和)。

所以回到本题,我们枚举K求出相应区间的所有第K位二进制位构成B数组,然后对B数组做异或和得到sum数组。

现在考虑tmp相关的求和式O(1)计算式。我们从index位置开始考虑,如果B【index】为1,那么想得到一个经过

index的最钟异或和为1的子串,子串的左边界-1的sum值只能为0,如果B【index】为零,左边界-1为1。所以只要

计算index左边右边0/1的个数然后对应相乘就好了。

AC代码:

using namespace std;
#define MAXN 100010
#define MAXM 1010
#define INF 1000000000
#define MOD 1000000061
#define ll long long
#define eps 1e-8
int n;
int a[MAXN];
int b[MAXN];
int v[MAXN];
int s[MAXN];
int l[MAXN],r[MAXN];
int st[MAXN],tp;
ll ans;
ll cal(){
	int i;
	ll re=0;
	for(i=1;i<=n;i++){
		s[i]=s[i-1]+v[i];
	}
	for(i=1;i<=n;i++){
		ll s1=i-l[i]+1;
		ll s2=r[i]-i+1;
		ll t1=s[i-1]-s[max(0,l[i]-2)];
		ll t2=s[r[i]]-s[i-1];
		(re+=t1*(s2-t2)%MOD*a[i])%=MOD;
		(re+=(s1-t1)*t2%MOD*a[i])%=MOD;
	}
	return re;
}
int main(){
	int i,j;
	int tmp;
	scanf("%d",&tmp);
	while(tmp--){
		ans=0;
		scanf("%d",&n);
		a[0]=a[n+1]=INF*2;
		for(i=1;i<=n;i++){
			scanf("%d",&a[i]);
			b[i]=b[i-1]^a[i];
		}
		st[tp=1]=0;
		for(i=1;i<=n;i++){
			while(a[st[tp]]<=a[i]){
				tp--;
			}
			l[i]=st[tp]+1;
			st[++tp]=i;
		}
		st[tp=1]=n+1;
		for(i=n;i;i--){
			while(a[st[tp]]<a[i]){
				tp--;
			}
			r[i]=st[tp]-1;
			st[++tp]=i;
		}
		for(i=0;i<31;i++){
			for(j=1;j<=n;j++){
				v[j]=(b[j]>>i)&1;
			}
			(ans+=cal()*(1<<i))%=MOD;
		}
		printf("%lld\n",ans);
	}
	return 0;
}
/*
3
1
61
5
1 2 3 4 5
5
10187 17517 24636 19706 18756
*/

The end;

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值