Codeforces 633 G. Yash And Trees (dfs序+线段树+位图)


题意: 给定一颗根为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;
}



猜你喜欢

转载自blog.csdn.net/u012891242/article/details/50822665
今日推荐