题意: 给定一颗根为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);
F的函数定义:
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));
假设位图只有m位,这样写不会出错,但是位图是定义了1000位的。
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;
}