[权值线段树]JZOJ 4417 神奇的字符串
分析
这题有很多表达没看懂,例如(N,A)=1什么的(毕竟我比较菜)
但是不影响写题(因为我不会写啊哈哈哈)
我们将c分成两类,相当于给c排了序,即0的部分下标就是0~p-1,1的部分下标就是p~n-1(也就是把题目所说式子转化为下标,数组元素才储存值)
然后我们发现这个排完序的数组是有序的(废话),通过思考(交流学习)可以发现,它们所对应的原下标也是有序的
x=x1-a*i(不考虑模)
那么我们就可以将s中每一个值都带进去得到一个开头区间,给区间加一
问题转化为询问一个点在多少个区间内出现,且区间可以改变
权值线段树可以解决这样的问题
#include <iostream> #include <cstdio> using namespace std; const int N=1e5+10; struct Seg { int l,r,cnt,lz; }s[10000010]; int cnt; char c[N]; int n,a,b,p,m,q; void Calc(int i,int &l,int &r) { int x=c[i]-'0'; if (x) l=0,r=p-1; else l=p,r=n-1; l=((l-a*i)%n+n)%n;r=((r-a*i)%n+n)%n; } void New(int x) { if (!s[x].l) s[x].l=++cnt; if (!s[x].r) s[x].r=++cnt; } void Pushdown(int x) { s[s[x].l].lz+=s[x].lz;s[s[x].l].cnt+=s[x].lz; s[s[x].r].lz+=s[x].lz;s[s[x].r].cnt+=s[x].lz; s[x].lz=0; } void Change(int x,int l,int r,int ll,int rr,int val) { if (ll<=l&&r<=rr) { s[x].cnt+=val;s[x].lz+=val; return; } New(x);Pushdown(x); int mid=l+r>>1; if (ll<=mid) Change(s[x].l,l,mid,ll,rr,val); if (mid<rr) Change(s[x].r,mid+1,r,ll,rr,val); } int Query(int x,int l,int r,int k) { if (l==r&&l==k) return s[x].cnt; New(x);Pushdown(x); int mid=l+r>>1; if (k<=mid) return Query(s[x].l,l,mid,k); else return Query(s[x].r,mid+1,r,k); } void Change(int l,int r,int val) { if (l<=r) Change(0,0,n-1,l,r,val); else Change(0,0,n-1,l,n-1,val),Change(0,0,n-1,0,r,val); } int main() { scanf("%d%d%d%d%d",&n,&a,&b,&p,&m); scanf("%s",c); for (int i=0;i<m;i++) { int l,r; Calc(i,l,r); Change(l,r,1); } scanf("%d",&q); while (q--) { char w[10];int x; scanf("%s%d",w,&x); if (w[0]=='Q') printf("%d\n",Query(0,0,n-1,(a*x+b)%n)); else { int l,r; Calc(x,l,r); Change(l,r,-1); c[x]=!(c[x]-'0')+'0'; Calc(x,l,r); Change(l,r,1); } } }
在日渐沉没的世界里,我发现了你。