【CF】1774E-Two Chess Pieces 题解

传送门:1774E
标签:深度优先搜索

题目大意

给定一棵含n个节点的树,已知在根节点1上有两个棋子。每个棋子都有它需要经过的一些节点,且任何时刻两棋子之间的距离不能超过d。你可以进行以下操作:选择一个棋子移动到与它此时位置相邻的节点上。求:让两棋子完成要求并回到根节点所需的最小操作数。

算法分析

  • 这是典型的树上搜索问题,我们应该第一时间想到dfs。但问题的关键在于两个棋子需要经过的点可能不一样,而且两棋子间的距离有限制。直接暴力吧。这个时候我们就要先找规律。
  • 稍微模拟一下不难发现,最小操作数正是两棋子走过的路径长度之和,而一个棋子在树上走过的路径长度正是它经过的节点数减1。那么我们只要用两个bool数组分别存放两个棋子经过的点,深搜后将所有的1相加即可得到答案。
  • 思路有了,还要具体分析dfs的过程。设两棋子分别为A、B,当前节点编号为x,那么我们就可以考虑每个点的三种情况(以下A、B可互换):
    1. A需要经过点x,但B不需要经过x,且B离A的距离大于d。于是我们将B移动至离A距离为d的位置,标记这个位置被B经过,再标记x被A经过。
    2. A需要经过点x,但B不需要经过x,且B离A的距离小于等于d。此时只要标记x被A经过。
    3. A、B都需要经过点x。于是我们将A、B都移动到x,并标记x被两棋子经过。
  • 接下来是本题的关键——树上动态规划
	p=G[x];
	while(p!=NULL){
		if(p->adjvex!=front){
			cal(x,p->adjvex);
			b11[x]=max(b11[x],b11[p->adjvex]);	//如果子节点被经过,则父节点必被经过
			b22[x]=max(b22[x],b22[p->adjvex]);
		}
		p=p->next;
	}
  • 还有最后一个问题,如何找到x的d级祖先呢?很简单,只要用一个栈存放根节点到x经过的所有点,top-d就是d级祖先,回溯时再top–即可。

代码实现

using namespace std;
int d,top=-1,stack[200001];		//存放根节点到x路上的所有点
long long sum=0LL;
bool b1[200001]={0},b2[200001]={0},b11[200001]={0},b22[200001]={0};
struct Item{
	int adjvex;
	struct Item *next;
}*G[200001];
void cal(int front,int x){
	struct Item *p;
	stack[++top]=x;
	if(b1[x]&&b2[x]){
		b11[x]=1;	//标记x被A经过 
		b22[x]=1;	//标记x被B经过 
	}
	else if(b1[x]){
		b11[x]=1;
		if(top>=d)
			b22[stack[top-d]]=1;	//寻找x的d级祖先 
	}
	else if(b2[x]){
		b22[x]=1;
		if(top>=d)
			b11[stack[top-d]]=1;
	}
	p=G[x];
	while(p!=NULL){
		if(p->adjvex!=front){
			cal(x,p->adjvex);
			b11[x]=max(b11[x],b11[p->adjvex]);	//如果子节点被经过,则父节点必被经过
			b22[x]=max(b22[x],b22[p->adjvex]);	//树上dp 
		}
		p=p->next;
	}
	top--;		//回溯 
}
int main(){
	int i,j,x,y,n,n1,n2;
	struct Item *p;
	std::ios::sync_with_stdio(false);
	std::cin.tie(0);std::cout.tie(0);
	cin>>n>>d;
	for(i=0;i<n-1;i++){		//邻接表建立无向边 
		cin>>x>>y;
		p=(struct Item *)malloc(sizeof(struct Item));
		p->adjvex=y;
		p->next=G[x];
		G[x]=p;
		p=(struct Item *)malloc(sizeof(struct Item));
		p->adjvex=x;
		p->next=G[y];
		G[y]=p;
	}
	cin>>n1;
	for(i=0;i<n1;i++){		//A需要经过的点 
		cin>>x;
		b1[x]=1;
	}
	cin>>n2;
	for(i=0;i<n2;i++){		//B需要经过的点 
		cin>>x;
		b2[x]=1;
	}
	cal(0,1);		//深度优先搜索 
	for(i=2;i<=200000;i++){
		sum+=b11[i]+b22[i];		//遍历求和 
	}
	cout<<2*sum;		//棋子需要回到根节点,路径长翻倍
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值