题意: 给定一颗根为1的树(十万节点),每个节点有一个价值value。
然后进行两种操作(十万操作):
1. 问一颗子树上的所有节点的价值在对m取余数之后,会产生多少种不同的质数。
2.将一颗子树上的所有节点的价值value都加上x
思路: dfs序+线段树+位图
首先是dfs序,对这棵树进行深度优先遍历(DFS)之后,按顺序将访问到的节点的价值排成一个数组(这个数组中的下标称作线段树下标,节点i的线段树下标是L[i]),
可以发现,同一颗子树的节点的价值都是挨在一起的。
于是,一次DFS之后,以每个节点为根的子树对应数组上的一段,用L[ ],R[ ]两个数组标出左右界限。
于是树形操作就化为了区间操作,明显变成线段树题了,区间+x,和区间询问两种操作。
由于m<=1000,而这里只看每个数的余数,m较小,所以可以用位图来做,线段树的每个节点都是一个m位的位图。
区间+x,就变成了区间所有位图循环移位x位(线段树懒惰标记)。
区间询问就是区间所有位图相或,再与质数位图相与,再数一数有几个二进制位为1.
注意:
1. 线段树不能直接使用价值数组V
将树形结构转换成链式结构(即化树为链)的过程中,节点 A 被移到了数组下标为L[A]的地方,
所以在线段树中,下标为L[A]的地方的value其实是V[A],而不是V[L[A]]。我在这里卡了好久。
所以代码中,化树为链的同时,用Q数组记下了线段树下标对应的价值,所以线段树中应该使用数组Q,而不是数组V。
2. 化树为链的实质
我在代码中的实现时手写了一个栈,代码看起来有点乱。
其实要是写成递归版本,代码就会清晰很多,但是由于题目给的树是不平衡的,所以程序会因为函数递归次数过多而崩溃。
虽然不实用,但是递归版本的有助于理解,在这里贴下代码:
主函数中的部分:
memset(vis,0,sizeof(vis)); vis[1]=1;IP=0; F(1);
bool vis[maxn]; void F(int rt){//DFS深搜 for(list<int>::iterator it=List[rt].begin();it!=List[rt].end();++it){//遍历一个List的标准写法 if(vis[*it]) continue;//两行判断是否已经访问过这个节点 else vis[*it]=1; L[*it]=++IP;//记录对应的L值 Q[IP]=V[*it];//记录Q数组的值 F(*it);//递归调用 } R[rt]=IP;//记录R值 }
3.位图循环移位的实现
首先位图长度m是不固定的,为了省事,直接定义的bitset<1000>作为位图来使用,只使用其中较低的m位。
bitset的位图是左大右小,所以+x等于循环左移x位。
假设位图一共m位,那么位图bm 循环左移x位的代码可以这么写:
bm = (bm<< x) | (bm >> (m - x));
bm<<x会让低m位以外的位置出现1,这个可能会引起错误,但是就这么提交居然AC了。
后来发现,由于最后要对PrimeBit进行一次相与,而在PrimeBit的计算中,只算了低m位,所以PrimeBit除了低m位以外都是0,
所以低m位以外的数位到底是1还是0,都不重要,反正不会影响答案,于是循环左移这么写就可以了。
记录一下这题犯得各种错误:
1.刚开始化树为链的部分居然天真的写了递归版本,后来一直Runtime Error 才发现递归会爆栈,然后改了过来。
2.位图刚开始自己写了一个位图结构,然后一直TLE(超时),最后还是用的bitset类
3.线段树中不能使用V数组,而是要使用Q数组(前文有讲),这是下标映射开始没想清楚。
4.整个题建树的过程我就弄错了,导致了连续5次的Runtime Error,果然还是树的题写的太少。
5.位图的循环移位,开始弄错了方向,右移跟左移弄反了,查了好久也没查出来,后来参考了别人的AC代码才注意到移位方向跟我是反的。
6.刚开始写反了线段树的Query中的与和或。
最后,这题居然有整整98个测试数据……提交之后一直盯着从Runing on test 1 看着它慢慢跳到98真是煎熬……
代码如下:
#include <iostream> #include <cstdio> #include <cmath> #include <cstring> #include <list> #include <bitset> #define out(i) <<#i<<"="<<(i)<<" " #define OUT1(a1) cout out(a1) <<endl #define OUT2(a1,a2) cout out(a1) out(a2) <<endl #define OUT3(a1,a2,a3) cout out(a1) out(a2) out(a3)<<endl #define maxn 100007 #define ls l,m,rt<<1 #define rs m+1,r,rt<<1|1 using namespace std; int n,m,q; //位图结构 typedef bitset<1000> Bitmap; Bitmap PrimeBit;//用来存放质数的位图 void SetPrimeBit(){//筛法求质数,得到质数位图PrimeBit (只包含小于m的质数) int p[1001]; memset(p,-1,sizeof(p)); PrimeBit.reset(); for(int i=2;i<m;++i){ if(~p[i]) continue; PrimeBit.set(i); for(int j=i;j<m;j+=i) p[j]=i; } } //出边表,偷懒用了List list<int> List[maxn]; //第i个节点的价值为V[i],对应在数组中的下标为 [ L[i],R[i] ] //IP是一个计数器,对于数组Q的解释:Q[L[i]]=V[i] int V[maxn],L[maxn],R[maxn],IP,Q[maxn]; //求L,R,Q数组 list<int>::iterator S[maxn]; int T[maxn], SP;//T[]记录栈中对应的节点编号,SP栈顶指针 bool vis[maxn];//visit数组,记录是否已经访问 void F(int rt){//DFS memset(vis,0,sizeof(vis)); IP=SP=0; S[++SP]=List[rt].begin(); T[SP]=rt; L[rt]=++IP; Q[IP]=V[rt]%m; vis[rt]=1; while(SP>0){ if(S[SP]!=List[T[SP]].end()){ rt = *(S[SP]++); if(vis[rt]) continue; else vis[rt]=1; S[++SP]=List[rt].begin(); T[SP]=rt; L[rt]=++IP; Q[IP]=V[rt]%m; } else{ R[T[SP]]=IP; --SP; } } } //线段树 Bitmap bm[maxn<<2];//线段树的位图 int Rotate[maxn<<2];//线段树循环位移位数 //下面是标准的线段树区间修改 void PushUp(int rt){//更新信息 bm[rt]=bm[rt<<1]|bm[rt<<1|1]; } void PushDown(int rt){//下推标记 if(Rotate[rt]){ Rotate[rt<<1]=(Rotate[rt<<1]+Rotate[rt])%m; Rotate[rt<<1|1]=(Rotate[rt<<1|1]+Rotate[rt])%m; bm[rt<<1]=(bm[rt<<1]<<Rotate[rt])|(bm[rt<<1]>>(m-Rotate[rt])); bm[rt<<1|1]=(bm[rt<<1|1]<<Rotate[rt])|(bm[rt<<1|1]>>(m-Rotate[rt])); Rotate[rt]=0; } } void Build(int l,int r,int rt){//建树 if(l==r){ bm[rt].reset(); bm[rt].set(Q[l]%m); Rotate[rt]=0; return; } int m=(l+r)>>1; Build(ls); Build(rs); PushUp(rt); } void Update(int L,int R,int X,int l,int r,int rt){//区间位图循环移位 if(L <= l && r <= R){ Rotate[rt]=(Rotate[rt]+X)%m; X%=m; bm[rt]=(bm[rt]<<X)|(bm[rt]>>(m-X)); return; } PushDown(rt); int m=(l+r)>>1; if(L <= m) Update(L,R,X,ls); if(R > m) Update(L,R,X,rs); PushUp(rt); } Bitmap Query(int L,int R,int l,int r,int rt){//查询区间位图相与 if(L <= l && r <= R){ return bm[rt]; } PushDown(rt); int m=(l+r)>>1; Bitmap ANS; if(L <= m) ANS = ANS | Query(L,R,ls); if(R > m) ANS = ANS | Query(L,R,rs); return ANS; } int main(void) { while(~scanf("%d%d",&n,&m)){ //计算质数位图 SetPrimeBit(); //初始化+读取数据 for(int i=1;i<=n;++i) List[i].clear(); for(int i=1;i<=n;++i) scanf("%d",&V[i]); for(int i=1;i<n;++i){ int a,b; scanf("%d%d",&a,&b); List[a].push_back(b); List[b].push_back(a); } //计算L,R,Q数组 F(1); //线段树建树 Build(1,n,1); //读取询问 scanf("%d",&q); for(int i=0;i<q;++i){ int op,v,x; scanf("%d%d",&op,&v); if(op==1){ scanf("%d",&x); //线段树区间修改 Update(L[v],R[v],x%m,1,n,1); } else{ //线段树区间查询 printf("%d\n",(Query(L[v],R[v],1,n,1)&PrimeBit).count()); } } } return 0; }