2018.10.30【校内模拟】字胡串(分治)

传送门


解析:

这道题虽然说容斥和分治的复杂度都是 O ( n ) O(n) O(n) l o g log log,但是容斥要处理二进制位,套的 l o g log log是值域的 l o g log log,而分治只有 O ( l o g n ) O(logn) O(logn),所以分治实际上是要比容斥快很多的。(也就快了大概一倍吧)

思路:

考虑每次处理 &lt; l , r &gt; &lt;l,r&gt; <l,r>,选择出一个分治中心 m i d mid mid,然后处理左端点在 &lt; l , m i d &gt; &lt;l,mid&gt; <l,mid>中,右端点在 &lt; m i d + 1 , r &gt; &lt;mid+1,r&gt; <mid+1,r>中的所有情况,然后递归处理 &lt; l , m i d &gt; &lt;l,mid&gt; <l,mid> &lt; m i d + 1 , r &gt; &lt;mid+1,r&gt; <mid+1,r>两个区间,显然这样是不重不漏的。

考虑这道题直接统计合法的方案数不太现实,所以转换一下,统计不合法的方案数,反正总方案数是 n 2 / 2 n^2/2 n2/2,但是显然左右端点重合的情况不可能合法,所以直接总方案数是 n × ( n − 1 ) / 2 n\times(n-1)/2 n×(n1)/2,然后分治下去。

首先位或和 m a x max max这两个操作都满足区间加法,那么处理所有点到分治中心的运算结果前缀和,那么需要查询某个区间(横跨分治中心)的时候就可以直接 O ( 1 ) O(1) O(1)
考虑处理每一个 l ≤ i ≤ m i d l\leq i \leq mid limid的位置 i i i,在 &lt; m i d + 1 , r &gt; &lt;mid+1,r&gt; <mid+1,r>中有多少个合法的右端点。

接下来是我们每次分治做到 O ( n ) O(n) O(n)的关键,解的单调性的证明。
首先所有前缀(后缀)位或和以及区间 m a x max max都是单调不减的。

显然对于任意区间 &lt; i , j &gt; &lt;i,j&gt; <i,j>,都有 g i , j ≥ f i , j g_{i,j}\geq f_{i,j} gi,jfi,j,所以不合法的情况只有 g i , j = f i , j g_{i,j}=f_{i,j} gi,j=fi,j

首先,如果一个区间满足上述性质,则对于它某个端点到分治中心(不妨假设是左端点)必然有 f i , m i d = g i , m i d f_{i,mid}=g_{i,mid} fi,mid=gi,mid,即它的左部分本身就不是一个合法区间。证明十分显然,因为现在考虑的是以当前左端点为最大值。

然后我们一直右移右端点 j j j直到下一个位置越界或者变得合法。

那么 &lt; m i d + 1 , j &gt; &lt;mid+1,j&gt; <mid+1,j>中的所有端点都可以作为当前不合法区间的右端点。

证明很简单,由于我们右移的策略就是新增的 j j j的两个运算不会影响原来的运算结果,并且远离分治中心的结果是单调的,所以解也是单调的。

注意处理右半边的时候不能考虑最大值相等的情况,因为相等的情况在左半部分已经考虑过了。


代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const

inline int getint(){
	re int num;
	re char c;
	while(!isdigit(c=gc()));num=c^48;
	while(isdigit(c=gc()))num=(num<<1)+(num<<3)+(c^48);
	return num;
}

cs int N=1000006;
int f[N],g[N],s[N];

ll ans;
inline void solve(int l,int r){
	if(l==r)return ;
	int mid=(l+r)>>1;
	solve(l,mid);solve(mid+1,r);
	
	f[mid]=g[mid]=s[mid];
	for(int re i=mid-1;i>=l;--i){
		f[i]=max(f[i+1],s[i]);
		g[i]=g[i+1]|s[i];
	}
	f[mid+1]=g[mid+1]=s[mid+1];
	for(int re i=mid+2;i<=r;++i){
		f[i]=max(f[i-1],s[i]);
		g[i]=g[i-1]|s[i];
	}
	for(int re i=mid,j=mid;i>=l;--i)
	if(f[i]==g[i]){
		while(j<r&&f[j+1]<=f[i]&&(g[i]|g[j+1])==g[i])++j;
		ans-=j-mid;
	}
	for(int re i=mid+1,j=mid+1;i<=r;++i)
	if(f[i]==g[i]){
		while(j>l&&f[j-1]<f[i]&&(g[i]|g[j-1])==g[i])--j;
		ans-=mid-j+1;
	}
}

int n;
signed main(){
	n=getint();
	for(int re i=1;i<=n;++i)s[i]=getint();
	ans=(ll)n*(n-1)/2;
	solve(1,n);
	cout<<ans;
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值