题目链接:Kingdom’s Power
题目大意:n个点构成一颗树,1号点是大本营可以无限派兵,每次可以移动一个军队走1步,问走遍n个点最少需要几步。
思路:
很容易想到这是一道树上DP的题,问题是我们每个结点应该维护什么值,以及结点间状态的转移。我们可以知道,如果我们要到达某个点,有两种方案,一种是调用已经派遣出去的军队,折返到达,一种是重新从起始点派兵,所以很容易想到要维护已经出去的军队和当前点的距离。那么我们对于每一颗节点维护2个值,一个是走完子树所有点用的最少总步数(默认此处初始时有一只军队),一个是当前子树中距离它最近的军队与它的距离,至于为什么维护最近的一个就可以,会在后文解释。
那么,我们使用dfs进行搜索,在最底层,节点的值会是(0,0),再不断回溯,更新父节点的值。对于当前节点,遇到第一个子节点时,值直接变成(vx+1,vy+1),也就是直接走下去,然后标记一下flag=true,遇到后面的子结点的时候,相当于(ux,uy)和(vx+1,vy+1)两个状态的合并过程。
这里我们就要进行分类讨论了,一共有三种情况。
- 第一种是从u最近子节点中的已经派出的军队折返到达,那么ux = ux+uy+vx+1,uy=vy+1,也就是折返走掉v,更新最近的位置为vy+1;
- 第二种是从起点重新发兵,那么ux = ux+dis[u]+vx+1,uy = min(uy,vy+1),这里对于uy只用维护一个最小值即可,因为被筛掉的那个值一定比dis[u]大,所以下次更新的时候也一定不会被选择,可以直接舍弃那只军队;
- 第三种情况则是改变初始行走策略,先走v,原因是从v走完后离u最近的点折返更划算,也就是vy+1<=uy,那么我们更新ux = ux+vx+1+vy+1,uy = uy。
很容易可以推出:当uy<=dis[u]&&uy<=vy+1的时候采取第一种情况,当dis[u]<=uy&&dis[u]<=vy+1的时候采取第二种情况,当vy+1<=uy&&vy+1<=dis[u]的时候采取第三种情况。最后的答案就是1号节点的状态值。
提醒:
本题多组数据,且数据量较大,记得清空数组,且不要用memset清空!!!
下面是我的AC代码:
#include<iostream>
#include<stdio.h>
using namespace std;
int T;
struct node{
int v,next;
}a[4000005];
int head[1000005];
int e = -1;
int dis[1000005];
int ans1[1000005];
int ans2[1000005];
void addnode(int u,int v){
a[++e].v = v;
a[e].next = head[u];
head[u] = e;
}
bool vis[1000005];
void dfs(int x){//预处理距离
vis[x] = true;
for(int i=head[x];i!=-1;i=a[i].next){
int v=a[i].v ;
if(vis[v])continue;
dis[v]=dis[x]+1;
dfs(v);
}
}
long long ans = 0;
void work(int x){
ans1[x]=ans2[x]=0;
vis[x]=true;
bool flag = false;
for(int i=head[x];i!=-1;i=a[i].next){
int v = a[i].v;
if(vis[v])continue;
work(v);
int tmpx=ans1[v]+1;
int tmpy=ans2[v]+1;
if(!flag){
ans1[x]=tmpx;
ans2[x]=tmpy;
flag = true;
}
else if(ans2[x]<=tmpy&&ans2[x]<=dis[x]){//最近军队折返
ans1[x]=ans1[x]+tmpx+ans2[x];
ans2[x]=tmpy;
}
else if(dis[x]<=ans2[x]&&dis[x]<=tmpy){//从起点发兵
ans1[x]=ans1[x]+dis[x]+tmpx;
ans2[x]=min(ans2[x],tmpy);
}
else if(tmpy<=ans2[x]&&tmpy<=dis[x]){//先走当前点
ans1[x]=ans1[x]+tmpx+tmpy;
}
}
// cout<<x<<" "<<ans1[x]<<" "<<ans2[x]<<" "<<dis[x]<<endl;
}
int main(){
scanf("%d",&T);
for(int Case = 1;Case<=T;Case++){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
head[i]=-1;
vis[i]=false;
}
e = -1;
for(int i=2;i<=n;i++){
int v;
scanf("%d",&v);
addnode(i,v);
addnode(v,i);
}
dis[1]=0;
dfs(1);
for(int i=1;i<=n;i++)
vis[i]=false;
work(1);
printf("Case #%d: %d\n",Case,ans1[1]);
}
}