题目大意
一棵 n n 个节点的树,有一个警察初始在点,速度为 1 1 ,树上分布有个罪犯,速度为任意大,如果罪犯和警察在同一个地方就被干掉了,警察希望干掉罪犯的时间尽量短,而罪犯希望最大化这个时间,假设每个人都以最优策略行动。求这个时间,如果警察不能干掉所有罪犯,那么输出”Terrorists win”。
数据范围
1≤n,m,wi≤50 1 ≤ n , m , w i ≤ 50 , wi w i 为边权
1 ≤xj ≤n,xj ≠ s 1 ≤ x j ≤ n , x j ≠ s
一个节点可能有多个罪犯
题解
这题看完就知道是树形dp,各种姿势的dp都可以过,下面介绍一种极不优雅的写法。
用 dp[u][v][s1][s2] d p [ u ] [ v ] [ s 1 ] [ s 2 ] 表示当前在第 u u 个点,将要走到第 个点,前面有 s1 s 1 个罪犯,后面有 s2 s 2 个罪犯,将所有罪犯干掉的最小时间。
考虑转移
首先,如果 v v 是叶子,下一步就可以直接干掉 个罪犯,可以直接从 dp[v][u][s2][0] d p [ v ] [ u ] [ s 2 ] [ 0 ] 转移。
现在假设 v v 点有个儿子,设在第 i i 个儿子分布了个罪犯,警察为了使时间尽量短,一定会先选择 dp[v][son[i]][a[i]][s1+s2−a[i]] d p [ v ] [ s o n [ i ] ] [ a [ i ] ] [ s 1 + s 2 − a [ i ] ] 最小的儿子;反过来,罪犯为了使时间尽量长,一定会使得 dp d p 值最小的儿子的 dp d p 值尽量大。所以枚举 v v 的每个儿子,开个辅助数组一下(其实就是把 ai a i 分成 k k 份的过程)。
时间复杂度? 可能是
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;
}