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

版权声明:转载请声明出处,谢谢配合。 https://blog.csdn.net/zxyoi_dreamer/article/details/83551113

传送门


解析:

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

思路:

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

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

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

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

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

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

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

那么 < m i d + 1 , j > <mid+1,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;
} 

猜你喜欢

转载自blog.csdn.net/zxyoi_dreamer/article/details/83551113