[Codeforces 868E] Policeman and a Tree

题目大意

一棵 n n 个节点的树,有一个警察初始在s点,速度为 1 1 ,树上分布有m个罪犯,速度为任意大,如果罪犯和警察在同一个地方就被干掉了,警察希望干掉罪犯的时间尽量短,而罪犯希望最大化这个时间,假设每个人都以最优策略行动。求这个时间,如果警察不能干掉所有罪犯,那么输出”Terrorists win”。

数据范围

1n,m,wi50 1 ≤ n , m , w i ≤ 50 wi w i 为边权

1xjn,xjs 1   ≤ x j   ≤ n , x j   ≠   s

一个节点可能有多个罪犯

题解

这题看完就知道是树形dp,各种姿势的dp都可以过,下面介绍一种极不优雅的写法。

dp[u][v][s1][s2] d p [ u ] [ v ] [ s 1 ] [ s 2 ] 表示当前在第 u u 个点,将要走到第v 个点,前面有 s1 s 1 个罪犯,后面有 s2 s 2 个罪犯,将所有罪犯干掉的最小时间。

考虑转移

首先,如果 v v 是叶子,下一步就可以直接干掉 s1 个罪犯,可以直接从 dp[v][u][s2][0] d p [ v ] [ u ] [ s 2 ] [ 0 ] 转移。

现在假设 v v 点有k个儿子,设在第 i i 个儿子分布了ai个罪犯,警察为了使时间尽量短,一定会先选择 dp[v][son[i]][a[i]][s1+s2a[i]] d p [ v ] [ s o n [ i ] ] [ a [ i ] ] [ s 1 + s 2 − a [ i ] ] 最小的儿子;反过来,罪犯为了使时间尽量长,一定会使得 dp d p 值最小的儿子的 dp d p 值尽量大。所以枚举 v v 的每个儿子,开个辅助数组dp一下(其实就是把 ai a i 分成 k k 份的过程)。

时间复杂度? 可能是O(n5)

PS:其实第二步转移可以换成二分,即二分一个最短时间(如果存在一种方案使得警察无法在这个时间内抓到所有人,那么就不合法)。这样比第一种方法容易理解一点。

#include<bits/stdc++.h>
using namespace std;

int n,m,cnt=0,root,ans=0x3f3f3f3f,head[60],a[60],cd[60],c[60][60],f[2][60],dp[60][60][60][60];
struct edge{
    int u,v,w,next;
}e[120];

void add(int u,int v,int w){
    e[++cnt]=(edge){u,v,w,head[u]};
    head[u]=cnt;
}

void dfs(int u,int fa){
    for(int i=head[u];i;i=e[i].next)
      if(e[i].v!=fa)
        dfs(e[i].v,u),a[u]+=a[e[i].v];
}

int solve(int u,int v,int s1,int s2){
    if(!s1&&!s2)return 0;
    if(!s1)return 0x3f3f3f3f;
    int &ret=dp[u][v][s1][s2];
    if(ret<0x3f3f3f3f)return ret;
    if(cd[v]==1){
        ret=solve(v,u,s2,0)+c[u][v];
        return ret;
    }
    for(int i=head[v];i;i=e[i].next)
      if(e[i].v!=u)
        for(int k=0;k<=s1;k++)
          solve(v,e[i].v,k,s1+s2-k);
    memset(f[1],0,sizeof(f[1]));
    f[1][0]=0x3f3f3f3f;
    bool b=0;
    for(int i=head[v];i;i=e[i].next)
      if(e[i].v!=u){
        memset(f[b],0,sizeof(f[b]));
        for(int j=0;j<=s1;j++)
          for(int k=0;k+j<=s1;k++)
            f[b][j+k]=max(f[b][j+k],min(f[b^1][j],solve(v,e[i].v,k,s1+s2-k)));
        b^=1;
      }
    ret=f[b^1][s1]+c[u][v];
    return ret;
}

int main(){
    int x,y,z;
    memset(dp,0x3f,sizeof(dp));
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);add(y,x,z);
        cd[x]++;cd[y]++;
        c[x][y]=c[y][x]=z;
    }
    scanf("%d%d",&root,&m);
    for(int i=1;i<=m;i++)scanf("%d",&x),a[x]++;
    dfs(root,0);
    for(int i=head[root];i;i=e[i].next)
      ans=min(ans,solve(root,e[i].v,a[e[i].v],m-a[e[i].v]));
    printf("%d",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值