2020CCPC秦皇岛K题题解

题目链接:Kingdom’s Power

题目大意:n个点构成一颗树,1号点是大本营可以无限派兵,每次可以移动一个军队走1步,问走遍n个点最少需要几步。

思路
很容易想到这是一道树上DP的题,问题是我们每个结点应该维护什么值,以及结点间状态的转移。我们可以知道,如果我们要到达某个点,有两种方案,一种是调用已经派遣出去的军队,折返到达,一种是重新从起始点派兵,所以很容易想到要维护已经出去的军队和当前点的距离。那么我们对于每一颗节点维护2个值,一个是走完子树所有点用的最少总步数(默认此处初始时有一只军队),一个是当前子树中距离它最近的军队与它的距离,至于为什么维护最近的一个就可以,会在后文解释。

那么,我们使用dfs进行搜索,在最底层,节点的值会是(0,0),再不断回溯,更新父节点的值。对于当前节点,遇到第一个子节点时,值直接变成(vx+1,vy+1),也就是直接走下去,然后标记一下flag=true,遇到后面的子结点的时候,相当于(ux,uy)和(vx+1,vy+1)两个状态的合并过程。

这里我们就要进行分类讨论了,一共有三种情况。

  1. 第一种是从u最近子节点中的已经派出的军队折返到达,那么ux = ux+uy+vx+1,uy=vy+1,也就是折返走掉v,更新最近的位置为vy+1;
  2. 第二种是从起点重新发兵,那么ux = ux+dis[u]+vx+1,uy = min(uy,vy+1),这里对于uy只用维护一个最小值即可,因为被筛掉的那个值一定比dis[u]大,所以下次更新的时候也一定不会被选择,可以直接舍弃那只军队;
  3. 第三种情况则是改变初始行走策略,先走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]);
 }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值