dfs序:
三道例题:(题目链接(virtual judge))
(1)New Year Tree CodeForces - 620E
(2) Weak Pair HDU - 5877
(3) Water Tree CodeForces - 343D
总结:
dfs序的作用:
题目中要求对节点的子树进行操作:那么利用dfs序:
将:题目中给的树---->转化为新的树
1 ,New Year Tree CodeForces - 620E
题目大意:对一个节点的子树执行区间修改颜色,并记录一共有几种颜色;
查询一个节点的子树一共有几种颜色;
题目中有几点需要注意的是:
(1)**题目给的信息一个树上的节点的信息,而不是一棵树上叶子节点的信息;**
而且初始时,每个节点的信息不同,所以要先dfs,找出每个节点的dfs序;
然后按照这个dfs序,重新建立一棵树,而题目中所给的树的每个节点,按照dfs序的大小,以此作为新树的叶子节点;
(2)60中颜色,可以利用状态压缩,保存在一个long long的值里面,注意:
1<<k 是不对的,这里1<<k会爆int,而1默认的int类型,所以需要下面的类型转换
1LL<<k,直接在1后面加LL(不需要typedef long long LL;也可以)
或者(long long)1<<k;
//New Year Tree CodeForces - 620E
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int num=400010;
struct node
{
int L,R,id;
long long sum;
}a[num<<2];
int d[num<<2],s[num<<2];
struct point
{
int v,next;
}e[num<<2];
int head[num],cnt;
int index,in[num<<2],out[num<<2];
void update(int k)
{
a[k].sum=a[k<<1].sum|a[k<<1|1].sum;
a[k].id=-1;
//a[k].id=-1.表示这个父节点不可以用于更新它的子节点
//因为此时父节点f,记录的是子节点s1和子节点s2的信息,
//如果再用f去更新,相当于把s2的信息添加到s1上
//把s1的信息添加到s2上,显然是不对的
return ;
}
void pushdown(int k)
{
a[k<<1].sum=a[k<<1|1].sum=a[k].sum;
//a[k<<1].id=a[k<<1|1].id=1;要去更新k<<1
//和k<<1的子区间
a[k<<1].id=a[k<<1|1].id=1;
//a[k].id=-1;,避免重复更新;
a[k].id=-1;
return ;
}
void build(int k,int L,int R)
{
a[k].L=L;a[k].R=R;
a[k].id=-1;a[k].sum=0;
if(L==R) {
a[k].sum=(long long)(1LL<<d[s[a[k].L]]);
return ;
}
int mid=(L+R)>>1;
build(k<<1,L,mid);
build(k<<1|1,mid+1,R);
update(k);
return ;
}
void change(int k,int L,int R,int x)
{
if(a[k].L>=L&&a[k].R<=R)
{
a[k].sum=(1LL<<x);
//a[k].id=1;需要去更新它的子区间
a[k].id=1;
return ;
}
if(a[k].id!=-1) pushdown(k);
int mid=(a[k].L+a[k].R)>>1;
if(L<=mid) change(k<<1,L,R,x);
if(R>mid) change(k<<1|1,L,R,x);
update(k);
return ;
}
long long search(int k,int L,int R)
{
if(a[k].L>=L&&a[k].R<=R)
{
return a[k].sum;
}
if(a[k].id!=-1) pushdown(k);
int mid=(a[k].L+a[k].R)>>1;
long long ans=0;
if(L<=mid) ans|=search(k<<1,L,R);
if(R>mid) ans|=search(k<<1|1,L,R);
return ans;
}
void addedge(int u,int v)
{
e[++cnt].v=v;
e[cnt].next=head[u];
head[u]=cnt;
return ;
}
void dfs(int u,int k)
{
s[++index]=u;
in[u]=index;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].v;
if(v==k) continue;
dfs(v,u);
}
out[u]=index;
return ;
}
int judge(long long ans)
{
int c=0;
while(ans)
{
if(ans&1) c++;
ans>>=1;
}
return c;
}
int main()
{
int n,m;
while(scanf("%d%d",&n,&m)!=EOF)
{
for(int i=1;i<=n;i++)
{
scanf("%d",&d[i]);
}
int u,v;
for(int i=2;i<=n;i++)
{
scanf("%d%d",&u,&v);
addedge(u,v);addedge(v,u);//以1为根的无向树;
}
dfs(1,0);
build(1,1,n);
int o;
for(int i=1;i<=m;i++)
{
scanf("%d",&o);
if(o==1){
scanf("%d%d",&u,&v);
change(1,in[u],out[u],v);
}
else if(o==2){
scanf("%d",&u);
long long ans=search(1,in[u],out[u]);
printf("%d\n",judge(ans));
}
}
}
return 0;
}
2,Weak Pair HDU - 5877
题目大意:查询每个节点和父节点的数值关系;
要求 au×av≤k. u是父亲节点,v是子节点,这里注意:如果1是2的父节点,3是1的父节点,那么3也是2的父节点;
信息转换: au×av≤k--> au≤k/av(考虑整数取舍问题,这样也对,av==0如何处理呢?);对于每个儿子节点来说,
只需要判断父亲节点中,有几个au比k/av小的即可。
解决问题:
按照dfs序的顺序,遍历到这个节点时,一定遍历了它的父节点(和这个节点的所在子树木的兄弟子树);
我们只需要保存父节点的值,去掉兄弟子树的值,就可以判断了;
这里用到了一个求逆序对的思想:维护一个树状数组,树状数组记录的信息是到目前位置,在这个数据范围内出现的
值的个数。
这样到了每个节点,我们就可以判断在节点的父节点au中,有几个比k/av小的数值;
注意:
1,所给节点信息是一棵树上所有节点的信息,而不是一棵树的叶子节点的信息,这道题dfs序的过程中就可以完成,
不需要记录每个点的in和out;
2,离散化问题
数据太大,需要离散化,而离散化时,需要处理两个数据:1,au;2,k/au;如果只是离散化au,比较时会出错;
3,删除左子树信息以及如何维护信息:
void dfs(int u)
{
int pos;
//查找当前位置k/au前面有几个值,但au==0时,对于af*au<=k,任意af都满足,所以查找所有已经插入的数据即可
pos=lower_bound(f+1,f+len+1,!a[u]?inf:k/a[u])-f;
//即使保存数据
ans+=getsum(pos);
//记录完这个点的信息,紧接着将这个点的信息插入
pos=lower_bound(f+1,f+len+1,a[u])-f;
build(pos,1);
for(int i=head[u];i;i=e[i].next)
dfs(e[i].v);
//处理完这个节点的子树,就将这个点信息删除,可以自己画一下,比较好理解
build(pos,-1);
return ;
}
//ac代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int num=2e5+10;
const int inf=0x3f3f3f3f;
int n,len;
int a[num],c[num*4];
long long ans,k,f[num];
bool vis[num];
struct node
{
int v,next;
}e[num];
int head[num],cnt;
void int_i(void)
{
memset(head,0,sizeof(head));
memset(c,0,sizeof(c));
memset(vis,0,sizeof(vis));
cnt=0;
return ;
}
void addedge(int u,int v)
{
e[++cnt].v=v;
e[cnt].next=head[u];
head[u]=cnt;
vis[v]=1;
return ;
}
int lowbit(int x)
{
return x&(-x);
}
void build(int pos,int x)
{
while(pos<=len)
{
c[pos]+=x;
pos+=lowbit(pos);
}
return ;
}
int getsum(int pos)
{
int ans=0;
while(pos>0)
{
ans+=c[pos];
pos-=lowbit(pos);
}
return ans;
}
void dfs(int u)
{
int pos;
pos=lower_bound(f+1,f+len+1,!a[u]?inf:k/a[u])-f;
ans+=getsum(pos);
pos=lower_bound(f+1,f+len+1,a[u])-f;
build(pos,1);
for(int i=head[u];i;i=e[i].next)
dfs(e[i].v);
build(pos,-1);
return ;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int_i();
scanf("%d%lld",&n,&k);
int c=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
f[++c]=a[i];
//离散化处理
if(a[i]==0)
f[++c]=inf;
else
f[++c]=k/a[i];
}
sort(f+1,f+c+1);
len=unique(f+1,f+c+1)-f-1;
int u,v;
for(int i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
addedge(u,v);
}
for(int i=1;i<=n;i++)
{
if(!vis[i])
{
ans=0;
dfs(i);
printf("%lld\n",ans);
break;
}
}
}
return 0;
}
3,Water Tree CodeForces - 343D
题目大意:
一个以1为根节点的树形水库,这些水库是连通的,父节点的水可以流向子节点,反之不可。
进行如下三种操作:
(1)在某个节点注水,(所有的子节点都会有水)
(2)将某个节点的水抽干,(所有的父节点的水都是空的)
(3)判断某个节点是否为空,(有一个子节点(和自己)为空,这个点就是空的)
注意:题目给的是一棵空树,对于每个节点都是0,这样建树的过程可以在dfs前面,但是新建的树的叶子节点是题目
所给树的所有节点。
注意:
(1)线段树维护的是子树中叶子点为1的数目;
除此之外,还有维护每个点管辖的区域,以及每个点的父节点
(2)如何执行操作(2)
如何将这个节点的所有父节点都置为空呢?
从第三个操作中知道,判断一个点为空:(有一个子节点(和自己)为空,这个点就是空的),那么我们只需要
将这个点置为0,那么每次它的所有的父节点查询的时候,这个点都会导致父节点查询到的子树中,节点不全为1;
(3)最为重要:
操作1中,对这个节点控制的区域全部赋值为1时,要判断一下,如果这个节点的子树中,存在0,要把这个节点的父节点
置为0;
因为可能在update的过程中,并没有更新到这个节点的父节点,这个父节点可能还是1(实际上这个父节点要是0,下次查询时可能就会出错)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int num=5e5+11;
struct node
{
int v,next;
}e[num<<2];
int head[num],cnt;
int in[num],out[num],index;
int f[num];
struct point
{
int L,R,sum,id;
}a[num<<2];
void addedge(int u,int v)
{
e[++cnt].v=v;
e[cnt].next=head[u];
head[u]=cnt;
return ;
}
void dfs(int u,int p)
{
in[u]=++index;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].v;
if(v==p) continue;
f[v]=u;
dfs(v,u);
}
out[u]=index;
return ;
}
void build(int k,int L,int R)
{
a[k].L=L;a[k].R=R;
a[k].sum=0;a[k].id=-1;
if(L==R) return ;
int mid=(L+R)>>1;
build(k<<1,L,mid);
build(k<<1|1,mid+1,R);
return ;
}
void pushdown(int k)
{
a[k<<1].sum=(a[k<<1].R-a[k<<1].L+1)*a[k].id;
a[k<<1|1].sum=(a[k<<1|1].R-a[k<<1|1].L+1)*a[k].id;
a[k<<1].id=a[k<<1|1].id=a[k].id;
a[k].id=-1;
return ;
}
void update(int k)
{
a[k].sum=a[k<<1].sum+a[k<<1|1].sum;
return ;
}
void change(int k,int L,int R,int x)
{
if(a[k].L>=L&&a[k].R<=R)
{
a[k].id=x;
a[k].sum=(a[k].R-a[k].L+1)*x;
return ;
}
int mid=(a[k].L+a[k].R)>>1;
if(a[k].id!=-1) pushdown(k);
if(L<=mid) change(k<<1,L,R,x);
if(R>mid) change(k<<1|1,L,R,x);
update(k);
return ;
}
int search(int k,int L,int R)
{
if(a[k].L>=L&&a[k].R<=R)
{
return a[k].sum;
}
if(a[k].id!=-1) pushdown(k);
int mid=(a[k].L+a[k].R)>>1;
int ans=0;
if(L<=mid) ans+=search(k<<1,L,R);
if(R>mid) ans+=search(k<<1|1,L,R);
return ans;
}
int main()
{
int n;
scanf("%d",&n);
build(1,1,n);
for(int i=1,u,v;i<n;i++)
{
scanf("%d%d",&u,&v);
addedge(u,v);addedge(v,u);
}
dfs(1,0);
int q;
scanf("%d",&q);
for(int i=1,u,v;i<=q;i++)
{
scanf("%d%d",&u,&v);
if(u==1)
{
int temp=search(1,in[v],out[v]);
if(v!=1&&temp!=(out[v]-in[v]+1)) change(1,in[f[v]],in[f[v]],0);
change(1,in[v],out[v],1);
}
else if(u==2)
{
change(1,in[v],in[v],0);
}
else if(u==3)
{
int temp=search(1,in[v],out[v]);
printf("%d\n",temp==(out[v] - in[v] +1));
}
}
return 0;
}