原题链接:http://codeforces.com/contest/343/problem/D
标签:线段树,dfs
大致题意:(搬运自dm的ppt)
这道题的解法如果没看过题解,是比较难想到的。这道题的解法就是读入完所有的树边后,用dfs搜一遍,得到每个节点的的儿子所在的区间,用in和out来表示。然后这道题就可以转化为线段树的问题。
那么如何实现三种操作呢?我们可以对于每个节点维护sum和f两个值。我们设用1表示该节点有水,0表示该节点没水,那么sum表示该点及其所有儿子的值的总和。用f来维护该点及其所有儿子是否都有(没)水。
对于操作1,如果该点存在值为0的儿子,那么把该点的父亲赋为0(想一想,为什么),然后进行一次区间修改即可。
对于操作2,直接将该点标记为空。
对于查询,将该点用query去查询已该点为根,有多少叶子节点的值为1,将该值与它的叶子节点数进行比较,即可得出子树内是否有节点值为0。如有,输出0;否则输出1。
这道题建议大家在调试时使用静态调试,不然需要调试很久(我自己就是这样)。
附上代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#define maxn 500050
#define pb push_back
using namespace std;
struct node
{
int l,r,f,sum;
};
int n,i,u,v,top,q;
int in[maxn+5],out[maxn+5],fa[maxn+5],dfn,vis[maxn+5]; //fa数组记录每个点的父亲
vector<int> t[maxn];
int ci,vi;
node tree[maxn*4+5];
void dfs(int x,int fat)
{
in[x]=++dfn; fa[x]=fat; vis[x]=1;
for(int h=0;h<t[x].size();h++)
if (vis[t[x][h]]==0) dfs(t[x][h],x);
out[x]=dfn;
}
void pushdown(int x) //利用f拆分计算sum
{
if (tree[x].f!=-1) //如果l~r全部已有水
{
tree[x*2].f=tree[x*2+1].f=tree[x].f;
tree[x*2].sum=(tree[x*2].r-tree[x*2].l+1)*tree[x].f;
tree[x*2+1].sum=(tree[x*2+1].r-tree[x*2+1].l+1)*tree[x].f;
tree[x].f=-1;
}
}
void build(int x,int l,int r)
{
tree[x].l=l; tree[x].r=r; tree[x].f=-1; tree[x].sum=0;
if (l!=r)
{
int mid=(l+r)/2; build(x*2,l,mid); build(x*2+1,mid+1,r);
}
}
void update(int x,int l,int r,int val) //区间修改
{
if (l<=tree[x].l&&tree[x].r<=r)
{
tree[x].f=val; tree[x].sum=val*(tree[x].r-tree[x].l+1);
}
else
{
pushdown(x); int mid=(tree[x].l+tree[x].r)/2;
if (l<=mid) update(x*2,l,r,val);
if (r>mid) update(x*2+1,l,r,val);
tree[x].sum=tree[x*2].sum+tree[x*2+1].sum;
}
}
int query(int x,int l,int r)
{
int maxl,minr;
if (tree[x].f!=-1)
{
maxl=max(l,tree[x].l); minr=min(r,tree[x].r);
return tree[x].f*(minr-maxl+1);
}
if (l<=tree[x].l&&tree[x].r<=r) return tree[x].sum;
else
{
int num=0; int mid=(tree[x].l+tree[x].r)/2;
if (l<=mid) num+=query(x*2,l,r);
if (r>mid) num+=query(x*2+1,l,r);
return num;
}
}
int main()
{
scanf("%d",&n); memset(fa,0,sizeof(fa)); memset(vis,0,sizeof(vis));
for (i=1;i<n;i++) scanf("%d%d",&u,&v),t[u].pb(v),t[v].pb(u);
dfn=0; dfs(1,0); build(1,1,dfn);
scanf("%d",&q);
while (q--)
{
scanf("%d%d",&ci,&vi);
if (ci==1)
{
if (query(1,in[vi],out[vi])<(out[vi]-in[vi]+1)&&fa[vi]!=0) //判断是否有儿子为空,并且它本身不是根节点
update(1,in[fa[vi]],in[fa[vi]],0); //将其父亲标记为空
update(1,in[vi],out[vi],1); //将其所有儿子标记为有水
}
if (ci==2)
update(1,in[vi],in[vi],0); //将该点标记为空
if (ci==3)
if (query(1,in[vi],out[vi])<(out[vi]-in[vi]+1)) printf("0\n");
else printf("1\n");
}
return 0;
}