CF1090H Linearization

传送门

先考虑什么样的串才符合条件.(s_i=P(x&i)oplus b),其实这里的(b)只能使得整体是否取反,所以可以先不管.然后考虑(x)的每个二进制位的对(s_0)(s_{len-1})贡献,可以发现如果(x)从小到大第(i)位为(1),那么会给从头开始每一个长度为(2^i)区间的后半部分所有(s_i)异或上(1),类似于这样$$egin{matrix}&underbrace{0,0,...0},&underbrace{1,1,...1},&underbrace{0,0,...0},&underbrace{1,1,...1}...&2{i-1}&2{i-1}&2{i-1}&2{i-1}end{matrix}$$

然后,根据上面的结论,不管(x)是多少,我们都可以发现这样的串,前半部分和后半部分要么完 全 一 致,要么完全不同,并且前半部分和后半部分也满足这个性质.

再考虑怎么求出最小操作次数.题目允许的操作是区间异或,显然可以改成两次前缀异或,还有就是两次前缀异或也可以反向合成一次区间异或.然后如果把串的差分数组搞出来(t_i=s_ioplus s_{i+1}),那么一次前缀操作就是对差分数组单点取反.这个差分数组也有一定性质,因为前面说到这个串前半部分和后半部分要么完全一致要么完全不同,所以这个差分数组的前后两半(不包括中间那个点)([1,mid))((mid,n])也是完全相同的,并且这两半也满足这个性质(至于中间那个点值是多少暂时不用管).用这个分治过程可以把串分成(log n)层,第(i)层都有(2^{i-1})个区间,并且每层的所有区间的中间点都要相同.所以每次把这些点找出来,然后对答案的贡献就是(0)个数和(1)个数的较小值

每次暴力统计是不行的,注意到每一层相邻两个数间距相等且为(2^k(k>1)),所以可以预处理出每个位置以及每次往后走(2^k)到达的位置的(0/1)个数的后缀和,然后在每一层直接找出这一层第一个中间点,用区间和贡献答案就好了

注意两次前缀操作可以合成为一次区间操作,所以应该输出(lceilfrac{ans}{2} ceil)

#include<bits/stdc++.h>
#define LL long long
#define uLL unsigned long long
#define db double

using namespace std;
const int N=2e5+10;
int rd()
{
    int x=0,w=1;char ch=0;
    while(ch<'0'||ch>'9') {if(ch=='-') w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
    return x*w;
}
char cc[N];
int n,a[N],q,sb[N][18];

int main()
{
    n=rd();
    scanf("%s",cc+1);
    for(int i=1;i<=n;++i) a[i]=cc[i]-'0';
	int lz=log2(n);
	for(int j=0;j<lz;++j)
		for(int i=n-1;i;--i)
			sb[i][j]=(i+(1<<(j+1))<=n?sb[i+(1<<(j+1))][j]:0)+(a[i]^a[i+1]);
    q=rd();
    while(q--)
    {
		int l=rd()+1,r=rd()+1,len=r-l+1,an=0;
		for(int i=len>>1,j=0;i;i>>=1,++j)
		{
			int ii=l+(1<<j)-1,x=sb[ii][j]-(ii+len<n?sb[ii+len][j]:0);
			an+=min(x,i-x);
		}
		printf("%d
",(an+1)/2);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/smyjr/p/11135113.html