http://codevs.cn/problem/1228/
思路
这个题咋一看就会想到树链剖分,但事实上,我们只需要将树链剖分中,由DFS序(当然这里的DFS序与树剖的不同,树剖的是先重链再轻边的DFS序,这里是纯粹的遍历顺序)确定树上节点在数据结构中的对应位置的方法提取出来。用每个点的DFS序,及以此点为根节点的子树中最大的DFS序确定每个子树的范围,用数据结构维护其区间和即可。
我们需要知道:
DFS序主要用于处理子树问题。
而树链剖分则主要用于处理树上两点间问题。
线段树代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define RI register int
using namespace std;
int n,m,ru,rv,tot,pos;
int limt;//当前最大dfs序
int first[200010],nxt[200010];
int seq[100010];//每个点的dfs序
char ch;
struct edge
{
int u,v;
}l[200010];
struct inte
{
int l,r,A;
}tree[400010];
struct point
{
int ls,rs;//每个点dfs序,以此点为根的子树中最大的dfs序
}p[100010];//以每个点为根的子树在seg上的范围
void add(int f,int t)
{
l[++tot]=(edge){f,t};
nxt[tot]=first[f];
first[f]=tot;
}
void dfs(int k,int fa)
{
seq[k]=++tot;
limt=tot;
for(RI i=first[k];i!=-1;i=nxt[i])
{
int x=l[i].v;
if(x!=fa)
dfs(x,k);
}
p[seq[k]].ls=seq[k];
p[seq[k]].rs=limt;
}
void update(int now)
{
tree[now].A=tree[now<<1].A+tree[now<<1|1].A;
}
void build(int now,int l,int r)
{
tree[now].l=l;
tree[now].r=r;
if(l==r)
{
tree[now].A=1;
return;
}
int mid=(l+r)>>1;
build(now<<1,l,mid);
build(now<<1|1,mid+1,r);
update(now);
}
void point_change(int now,int pos)
{
if(tree[now].l==tree[now].r)
{
tree[now].A=tree[now].A^1;
return;
}
int mid=(tree[now].l+tree[now].r)>>1;
if(pos<=mid)
point_change(now<<1,pos);
if(pos>mid)
point_change(now<<1|1,pos);
update(now);
}
int ask_sum(int now,int l,int r)
{
if(tree[now].l>=l&&tree[now].r<=r)
{
return tree[now].A;
}
int ans=0;
int mid=(tree[now].l+tree[now].r)>>1;
if(l<=mid)
ans+=ask_sum(now<<1,l,r);
if(r>mid)
ans+=ask_sum(now<<1|1,l,r);
return ans;
}
int main()
{
memset(first,-1,sizeof(first));
scanf("%d",&n);
for(RI i=1;i<=n-1;i++)//别再错了这是颗n-1条边
{
scanf("%d%d",&ru,&rv);
add(ru,rv);
add(rv,ru);
}
tot=0;
dfs(1,0);
build(1,1,n);
scanf("%d",&m);
for(RI i=1;i<=m;i++)
{
ch=getchar();
while(ch!='C'&&ch!='Q')
ch=getchar();
scanf("%d",&pos);
if(ch=='C')
{
point_change(1,seq[pos]);//线段树上的对应位置为其dfs序
}
if(ch=='Q')
{
printf("%d\n",ask_sum(1,p[seq[pos]].ls,p[seq[pos]].rs));
}
}
return 0;
}
树状数组代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define RI register int
using namespace std;
int n,m,ru,rv,tot,pos;
int limt;//当前最大dfs序
int first[200010],nxt[200010];
int seq[100010];//每个点的dfs序
int c[100010];
char ch;
struct edge
{
int u,v;
}l[200010];
struct point
{
int ls,rs;//每个点dfs序,以此点为根的子树中最大的dfs序
}p[100010];//以每个点为根的子树在BIT上的范围
int lowbit(int x)
{
return x&(-x);
}
void build(int f,int t)
{
l[++tot]=(edge){f,t};
nxt[tot]=first[f];
first[f]=tot;
}
void dfs(int k,int fa)
{
seq[k]=++tot;
limt=tot;
for(RI i=first[k];i!=-1;i=nxt[i])
{
int x=l[i].v;
if(x!=fa)
dfs(x,k);
}
p[seq[k]].ls=seq[k];
p[seq[k]].rs=limt;
}
void add(int k,int value)
{
for(int i=k;i<=n;i+=lowbit(i))
c[i]+=value;
}
int ask_sum(int l,int r)
{
int ans=0;
for(int i=r;i>0;i-=lowbit(i))
ans+=c[i];
for(int i=l-1;i>0;i-=lowbit(i))//注意边界
ans-=c[i];
return ans;
}
int main()
{
memset(first,-1,sizeof(first));
scanf("%d",&n);
for(RI i=1;i<=n-1;i++)
{
scanf("%d%d",&ru,&rv);
build(ru,rv);
build(rv,ru);
}
tot=0;
dfs(1,0);
for(RI i=1;i<=n;i++)
add(i,1);//建立树状数组
scanf("%d",&m);
for(RI i=1;i<=m;i++)
{
ch=getchar();
while(ch!='C'&&ch!='Q')
ch=getchar();
scanf("%d",&pos);
if(ch=='C')
{
if((c[seq[pos]]-ask_sum(seq[pos]-lowbit(seq[pos])+1,seq[pos]-1))==1)//单点查询
//左子树及自身的值之和-ask_sum(左子树第一个元素,左子树最后一个元素)
//或者写if(ask_sum(seq[pos],seq[pos])==1)
add(seq[pos],-1);
else add(seq[pos],1);
}
if(ch=='Q')
{
printf("%d\n",ask_sum(p[seq[pos]].ls,p[seq[pos]].rs));
}
}
return 0;
}
话说我要是再写一个分块版本好像就可以凑一个 >形形色色的苹果树< ….
不过由于本题比较简单就不再赘述分块版本了。