基于边的树形DP+DP转移一个神奇的二分。
二分要从罪犯的角度去理解,之前一直在纠结一个警察和罪犯的极大极小博弈,没想到可以有这么一个神奇的二分。
写完AC,但感觉理解得不够深刻。
#include<stdio.h>
#include<string.h>
#include<vector>
#include<algorithm>
using namespace std;
#define INF 0x7f000000
#define N 55
struct EDGE{
int u,v,w;
EDGE(int u=0,int v=0,int w=0):u(u),v(v),w(w){}
};
vector<EDGE> e;
vector<int> g[N];
int f[N*2][N][N];
int n,m;
int ccnt[N];
bool isleaf[N];
int st;
void read(){
scanf("%d",&n);
for (int i=1;i<n;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
e.push_back(EDGE(u,v,w));
e.push_back(EDGE(v,u,w));
g[u].push_back(e.size()-2);
g[v].push_back(e.size()-1);
}
scanf("%d",&st);
scanf("%d",&m);
memset(ccnt,0,sizeof(ccnt));
for (int i=1;i<=m;i++){
int x;
scanf("%d",&x);
ccnt[x]++;
}
memset(isleaf,0,sizeof(isleaf));
for (int i=1;i<=n;i++)
if (g[i].size()==1)
isleaf[i]=true;
}
int dfs(int u,int p){
for (int i=0;i<g[u].size();i++){
EDGE ed=e[g[u][i]];
if (ed.v==p)continue;
ccnt[u]+=dfs(ed.v,u);
}
//printf("ccnt[%d]=%d\n",u,ccnt[u]);
return ccnt[u];
}
int dp(int eid,int s,int s2){ //沿着eid走
int &fval=f[eid][s][s2];
if (fval<INF) return fval;
if (s==0) return 0;
if (s2==0) exit(-2);
EDGE ed=e[eid];
int u=ed.u;int v=ed.v;int w=ed.w;
//到达叶子节点//抓住s2个罪犯,往回走
if (isleaf[v]){
fval=w+dp(eid^1,s-s2,s-s2);
//printf("fval=%d\n",fval);
return fval;
}
//非叶子节点
//神奇的二分,找到T,表示警察能抓到所有罪犯最小时间
int l=1,r=N*N*N;
while(l<r){
int T=(l+r)/2;
int sum=0; //不让警察在T时间内抓住的最大罪犯个数
for (int i=0;i<g[v].size();i++){
int v2=e[g[v][i]].v;
int w2=e[g[v][i]].w;
if (v2==u) continue; //不走回头
//找到尽量大的b,使得当走e[g[u][i]]这条边,可以在<=T的时间内抓住所有罪犯
int b;
for(b=1;b<=s2;b++){
if (dp(g[v][i],s,b)<=T)
break;
}
if (b>s2)
sum=s2;
else
sum+=b-1;
if (sum>=s2) break;
}
if (sum>=s2) //存在一种方案使得警察无法在T时间内抓到所有人
l=T+1;
else
r=T;
}
fval=w+l;
return fval;
}
int main(){
read();
dfs(st,0);
memset(f,127,sizeof(f));
int ans=INF;
for (int i=0;i<g[st].size();i++){
EDGE ed=e[g[st][i]];
if (ccnt[ed.v])
ans=min(ans,dp(g[st][i],m,ccnt[ed.v]));
}
printf("%d\n",ans);
//printf("%d",dp(3,1,1));
return 0;
}