dfs序列小结+入门例题+New Year Tree CodeForces - 620E+ Weak Pair HDU - 5877+Water Tree CodeForces - 343D

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序的大小,以此作为新树的叶子节点;

(260中颜色,可以利用状态压缩,保存在一个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是子节点,这里注意:如果12的父节点,31的父节点,那么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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CodeForces - 616D是一个关于找到一个序列中最长的第k好子段的起始位置和结束位置的问题。给定一个长度为n的序列和一个整数k,需要找到一个子段,该子段中不超过k个不同的数字。题目要求输出这个序列最长的第k好子段的起始位置和终止位置。 解决这个问题的方法有两种。第一种方法是使用尺取算法,通过维护一个滑动窗口来记录\[l,r\]中不同数的个数。每次如果这个数小于k,就将r向右移动一位;如果已经大于k,则将l向右移动一位,直到个数不大于k。每次更新完r之后,判断r-l+1是否比已有答案更优来更新答案。这种方法的时间复杂度为O(n)。 第二种方法是使用枚举r和双指针的方法。通过维护一个最小的l,满足\[l,r\]最多只有k种数。使用一个map来判断数的种类。遍历序列,如果当前数字在map中不存在,则将种类数sum加一;如果sum大于k,则将l向右移动一位,直到sum不大于k。每次更新完r之后,判断i-l+1是否大于等于y-x+1来更新答案。这种方法的时间复杂度为O(n)。 以上是两种解决CodeForces - 616D问题的方法。具体的代码实现可以参考引用\[1\]和引用\[2\]中的代码。 #### 引用[.reference_title] - *1* [CodeForces 616 D. Longest k-Good Segment(尺取)](https://blog.csdn.net/V5ZSQ/article/details/50750827)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Codeforces616 D. Longest k-Good Segment(双指针+map)](https://blog.csdn.net/weixin_44178736/article/details/114328999)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值