传送门:1774E
标签:深度优先搜索
题目大意
给定一棵含n个节点的树,已知在根节点1上有两个棋子。每个棋子都有它需要经过的一些节点,且任何时刻两棋子之间的距离不能超过d。你可以进行以下操作:选择一个棋子移动到与它此时位置相邻的节点上。求:让两棋子完成要求并回到根节点所需的最小操作数。
算法分析
- 这是典型的树上搜索问题,我们应该第一时间想到dfs。但问题的关键在于两个棋子需要经过的点可能不一样,而且两棋子间的距离有限制。
直接暴力吧。这个时候我们就要先找规律。 - 稍微模拟一下不难发现,最小操作数正是两棋子走过的路径长度之和,而一个棋子在树上走过的路径长度正是它经过的节点数减1。那么我们只要用两个bool数组分别存放两个棋子经过的点,深搜后将所有的1相加即可得到答案。
- 思路有了,还要具体分析dfs的过程。设两棋子分别为A、B,当前节点编号为x,那么我们就可以考虑每个点的三种情况(以下A、B可互换):
- A需要经过点x,但B不需要经过x,且B离A的距离大于d。于是我们将B移动至离A距离为d的位置,标记这个位置被B经过,再标记x被A经过。
- A需要经过点x,但B不需要经过x,且B离A的距离小于等于d。此时只要标记x被A经过。
- 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;
}