题目连接
思路参考
题目显然是一个树形DP,我们用dp[ i ][ j ]表示已经询问了子树i的所有关键节点,人车的一个状态。其中
j==0:人去,不管人是否回来
j==1:人去,人一定要回来
j==2:人车都去,人车都要回来
j==3:人车都去,人一定要回来,车不管
j==4:人车都去,不管人车最后是否回来。
注意,这里显然有 dpi>=dp[i][0] ,dpi>=dpi>=dp[i][3]>=dp[i][4]我们先考虑比较容易计算的,我们记人走边(fa,son)的花费为w1(fa,son),车为w2(fa,son),显然
dpfa = ∑ dpson + 2*w1(fa,son)
dpfa = ∑ min( dpson +2* w1(fa,son) , dpson + 2*w2(fa,son) )
现在我们考虑复杂一点的情况,dp[fa][0]和dp[fa][3]
对dp[fa][0],其实就是有一个边选择为 dp[son][0] + w1(fa,son) 而其他的都选择 dpson + 2*w1(fa,son);或者都选后者,我们记
temp=0,temp = min(dp[son][0] - dp[son][1] - w1(fa,son) )
dp[fa][0] = dpfa + temp
则;
t2 = min( dpson +2* w1(fa,son) , dpson + 2*w2(fa,son) )`
现在解决dp[fa][3],为了解释的方便,我们记
dp[fa][3]的决策方式,其实就是一个选择 w2(fa,son) + dp[son][3] + w1(fa,son) ,其他的选t2,或者都选t2,这样我们得到dp[fa][3]的转移方程t3=0,t3=min( t3 , w1(fa,son)+ w2(fa,son)+ dp[son][3] - t2 );
dp[fa][3] = dpfa + t3;
dp[fa][4]则是一个比较复杂的过程,我们从走的最后一棵子树考虑
1、走最后一棵子树时,还有车这样的情况还是很好计算的,最后一棵子树的选择是 min( w1(fa,son) + dp[son][0] , w2(fa,son) + dp[son][4] ) 而其他选择都是t2。或者全部选择t2。
2、走最后一棵子树时,没有车了。
这种情况大概就是,最后一棵子树的决策是w1(fa,son)+w2(fa,son)+dp[son][3];其他的,某一棵子树,人车走下去,然后只有人回来了,剩下的决策是t2。
这个情况是最麻烦的,因为有两棵特殊的子树,我们类似上面的差值统计的时候,要避免两个差值代表同一个子树。为了解决这个问题,我最开始的方法是,先后将两个差值的优先级设为不同,这样统计的话,可以避免重复子树。但是因为顺序处理没处理好,我的代码最开始有BUG。但是因为能A这题,所以我没注意到。
这里多谢swwlqw的指点,这个问题现在已经改正。
我们记
ff1 = w1(fa,son)+ w2(fa,son)+ dp[son][3] - t2;
ff2 = w1(fa,son)+ dp[son][0] - t2;
我们的目标是找到不同son,使得ff1 + ff2 最小。我们可以先去找ff1的最小和次小,并记录达到最小ff1的儿子match。再遍历一下所有儿子,如果此时的son就是match,那么就配合ff1的次小值,否则配合最小值。
或者直接记录下来ff1和ff2的最小和次小,最后在做选择。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
using namespace std;
const int MAXN=1000010;
typedef unsigned long long int ll65;
typedef long long int ll64;
typedef struct node{
struct node *next;
int c0,c1,v;
}node;
node buf[2000010];
int bufsize=0;
bool vis[MAXN]={false};
node *head[MAXN]={NULL};
ll64 dp[MAXN][5];
int add(int u,int v,int c0,int c1){
node *p=buf+bufsize++;
p->v=v; p->c0=c0; p->c1=c1;
p->next=head[u]; head[u]=p;
p=buf+bufsize++;
p->v=u; p->c0=c0; p->c1=c1;
p->next=head[v]; head[v]=p;
return 0;
}
int dfs(int u,int fa){
node *p=head[u];
for(;p!=NULL;p=p->next){
int v=p->v;
if(v==fa) continue;
dfs(v,u);
vis[u]|=vis[v];
}
if(vis[u]==true){
ll64 tmp0=0,tmp2=0,tmp3=0,tmp41=0;
int match=-1,match1=-1;
ll64 ff1,t1=1LL<<50,t2=1LL<<50,ff2,f1,f2;
f1=f2=t1=t2=1LL<<50;
dp[u][0]=dp[u][1]=dp[u][2]=dp[u][3]=dp[u][4]=0LL;
for(p=head[u];p!=NULL;p=p->next){
int v=p->v;
if(v==fa) continue;
if(vis[v]==true){
dp[u][1]+=dp[v][1]+ p->c0 * 2;
tmp2=min(dp[v][1]+p->c0*2,dp[v][2]+p->c1*2);
dp[u][2]+= tmp2;
tmp0=min(tmp0,dp[v][0]-dp[v][1]-p->c0);
tmp3=min(tmp3,p->c0+p->c1+dp[v][3]-tmp2);
tmp41=min(tmp41, min(p->c0+dp[v][0],p->c1+dp[v][4])-tmp2);
ff1=p->c0+dp[v][0]-tmp2;
if(ff1<f1){
match1=v;
f2=f1;
f1=ff1;
}else f2=min(ff1,f2);
ff2=p->c0+p->c1+dp[v][3]-tmp2;
if(ff2<t1){
match=v;
t2=t1;
t1=ff2;
}else t2=min(ff2,t2);
}
}
dp[u][0]=dp[u][1]+tmp0;
dp[u][3]=dp[u][2]+tmp3;
dp[u][4]=dp[u][2]+tmp41;
dp[u][4]=min(dp[u][3],dp[u][4]);
if(match==match1){
dp[u][4]=min(dp[u][4],dp[u][2]+min(f1+t2,f2+t1));
}else{
dp[u][4]=min(dp[u][4],dp[u][2]+f1+t1);
}
}
return 0;
}
int main()
{
int n,m,i,j,k,u,v,c0,c1;
scanf("%d",&n);
for(i=1;i<n;i++){
scanf("%d %d %d %d",&u,&v,&c0,&c1);
add(u,v,c0,c1);
}
scanf("%d",&m);
for(i=1;i<=m;i++){
scanf("%d",&k);
vis[k]=true;
}
dfs(1,-1);
cout<<min(dp[1][0],dp[1][4])<<endl;
return 0;
}
扫码关注,定期分享技术、算法类文章