GMOJ 4417. 【HNOI2016模拟4.1】神奇的字符串 题解

注意: 所有位置从0开始。

不瞒你说我一开始以为是数学题

对于这题,我们发现原字符串是存不下的,于是它肯定与字符串算法无关。数学题

首先我们尝试维护对每一个位置为开头的答案是多少,但是我们发现直接维护不可取。于是反过来想,对每一个字符,从哪里为起点可以让这一位产生贡献(即对于一个在 (s) 中的位置 (i) ,寻找一堆 (x) 使得 (c_{x+i} ot= s_i) )。

但是对于有贡献的起点位置集,这玩意碎了一地,显然是不好维护的。于是我们按照 ((a imes x+b)\%n) 的值排序,对于样例,排序的效果如下:(接下来配合这几个串食用更佳)

012345678|位置
---------+----
627384051|值
101011010|原串
---------+----
012345678|值
000011111|新串

假设现在有一个第一位的1,我们马上就可以看出值为 (0 ext{~}3) 都是有贡献的起点位置( 值为 (0 ext{~}3) 的字符为0)。

对于第二位假设还是1,与这个1不一样的字符仍然是值为 (0 ext{~}3) 这几个位置,但问题在于我们要的是起点,而我是第二位,起点并不是我匹配的这一位,而是我在匹配 (c) 的时候的前一位。于是真正对我有贡献的起点就会发生变化。

变化就是在原串中有贡献的一堆0左移一位,这样我才能在下一位拿到一个0使我产生贡献。

那么原串左移一位对新串造成了什么影响?

还是样例:

graph LR 6[6] --+5--> 2[2] 2[2] --+5--> 7[7] 7[7] --+5--> 3[3] 3[3] --+5--> 8[8] 8[8] --+5--> 4[4] 4[4] --+5--> 0[0] 0[0] --+5--> 5[5] 5[5] --+5--> 1[1]

可以注意到每一个位置到下一个位置都加了一个 (a) ,那么反过来到上一个位置就减了一个 (a) 。那么对于新串的值,也都是减一个 (a) 。由于值是连续的,减去一个 (a) 相对于向左循环移动 (a) 位,它还是一个完整的区间。

可以看出对于每一个 (s) 中字符有贡献的起点位置值都是一段完整的区间(实际做时因为会从0截断所以可能会变成两段,不过不影响,如果是环形的话就是完整的一段)。那么对于某一个位置值,(s) 中的多少个字符以这个值为起点有贡献,就是以这个值为起点的答案。注意这里的位置并不是原串 (c) 中的位置,而是 (c) 中的。对于一个 (c) 中的位置 (x) ,它在新的排序串中的位置值为 ((a imes x+b)\%n)

于是现在问题就很简单了,对于一个1,我们对 (0 ext{~}p-1) 左移若干位后的区间加1(位移的方法是对于第 (i) 位整个区间左移 (i imes a) 位,即左右端点减去左移值)。对于一个0,我们对 (p ext{~}n) 左移后的区间加1。对于查询,我们找到查询的位置 (x) 在排序串中的位置,看看这个位置被修改了多少(对多少个 (s) 中的字符有贡献),输出即可。

就比如这样:

graph LR subgraph 第一位的1 subgraph 第二位的0 左 subgraph 第三位的1 左 0[0] --- 1[0] 1[0] --- 2[0] end 2[0] --- 3[0] end end 3[0] --- 4[1] 4[1] --- 5[1] 5[1] --- 6[1] 6[1] --- 7[1] 7[1] --- 8[1] subgraph 第二位的0 右 subgraph 第三位的1 右 8[0] end end

图真丑

那么对于第一个位置(最左边的0),这个位置被三个字符打上了有贡献的标记,于是这个值的答案就是3。

总之就是对每一个字符,把一个特定区间内的值打一个标记。对于一个,这个值的答案就是被标记的数量。

Code

然后就是记得动态开点,可以避免离散化。

#include<cstdio>
#define mid ((l+r)>>1)
using namespace std;
struct tree{
	int l,r,v;
}t[12000010];
int last,n,a,b,p,q,m;
char s[100010];
template<typename T>void read(T &x){
	char c=getchar();
	for(;47>c||c>58;c=getchar());
	for(x=0;47<c&&c<58;x=x*10+c-48,c=getchar());
}
void read(char &x){
	for(x=getchar();x<33;x=getchar());
}
void sg(int m,int x,int y,int c,int l,int r){    //区间修改
	if(x<=l&&r<=y){
		t[m].v+=c;
		return;
	}
	if(x<=mid){         //左半
		if(!t[m].l){
			t[m].l=++last;
		}
		sg(t[m].l,x,y,c,l,mid);
	}
	if(y>mid){         //右半
		if(!t[m].r){
			t[m].r=++last;
		}
		sg(t[m].r,x,y,c,mid+1,r);
	}
}
int call(int m,int x,int l,int r){    //查询
	int re=t[m].v;
	if(x<=mid&&t[m].l){           //左半
		re+=call(t[m].l,x,l,mid);
	}else if(x>mid&&t[m].r){      //右半
		re+=call(t[m].r,x,mid+1,r);
	}
	return(re);
} 
void add(int x,int y,int c){      //对某个区间进行取模,拆分
	if((x=(x%n+n)%n)<(y=(y%n+n)%n)){
		sg(0,x,y,c,0,n-1);
	}else{
		sg(0,0,y,c,0,n-1);
		sg(0,x,n-1,c,0,n-1);
	}
}
int main(){
	read(n);read(a);read(b);read(p);read(m);
	for(int i=0;i<m;i++){
		read(s[i]);
		if(s[i]==49){
			add(-a*i,p-a*i-1,1);
		}else{
			add(p-a*i,n-a*i-1,1);
		}
	}
	read(q);
	for(int i=1;i<=q;i++){
		char c;
		int x;
		read(c);read(x);
		if(c=='Q'){
			printf("%d
",call(0,(x*a+b)%n,0,n-1));
		}else{
			if(s[x]==49){
				add(-a*x,p-a*x-1,-1);
				add(p-a*x,n-a*x-1,1);
			}else{
				add(p-a*x,n-a*x-1,-1);
				add(-a*x,p-a*x-1,1);
			}
			s[x]=97-s[x];
		}
	}
}
原文地址:https://www.cnblogs.com/groundwater/p/13503404.html