逐个击破
一道很好的树形DP!
这道题需要分类讨论。我们用dp[i][0]表示以i为根的子树内没有敌人的最小花费,dp[i][1]表示以i为根的子树内有敌人的最小花费,根据题目描述,合法情况只能有一个敌人。至于有没有敌人,我们可以通过先记录一下哪里有敌人,之后进行一下按位或。
考虑当前如果根节点有敌人,那么显然dp[i][0]就是INF(不合法),而dp[i][1]的话,就要把所有有敌人的子树全部切断,也就是取min(dp[v][0],dp[v][1] + e[i].v)
如果当前根节点没有敌人,那么dp[i][0]就和上面的dp[i][1]转移是一样的,而dp[i][1],我们就允许保留一个不切断,我们还是一样先转移,之后枚举一遍保留dp[v][1]的最小值即可。(也就是我们相当于减去原来切断的花费,加上保留的花费)
之后就完成啦。(%%%prophetB)看一下代码。
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #include<set> #include<queue> #define rep(i,a,n) for(int i = a;i <= n;i++) #define per(i,n,a) for(int i = n;i >= a;i--) #define enter putchar('\n') using namespace std; typedef long long ll; const int M = 100005; const int N = 20005; const ll INF = 100000000000009; const double eps = 1e-4; int read() { int ans = 0,op = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') op = -1; ch = getchar(); } while(ch >= '0' && ch <= '9') { ans *= 10; ans += ch - '0'; ch = getchar(); } return ans * op; } struct edge { int next,to,v; }e[M<<1]; int n,k,x,head[M],ecnt,y,z; ll dp[M][2],ans; bool vis[M],col[M]; void add(int x,int y,int z) { e[++ecnt].to = y; e[ecnt].v = z; e[ecnt].next = head[x]; head[x] = ecnt; } void dfs(int x,int fa) { col[x] = vis[x]; ll tot = 0,minn = INF; for(int i = head[x];i;i = e[i].next) { if(e[i].to == fa) continue; dfs(e[i].to,x); col[x] |= col[e[i].to],tot += min(dp[e[i].to][0],dp[e[i].to][1] + e[i].v); if(col[e[i].to]) minn = min(minn,dp[e[i].to][1] - min(dp[e[i].to][0],dp[e[i].to][1] + e[i].v)); } if(vis[x]) dp[x][0] = INF,dp[x][1] = tot; else dp[x][0] = tot,dp[x][1] = tot + minn; } int main() { n = read(),k = read(); rep(i,1,k) x = read() + 1,vis[x] = 1; rep(i,1,n-1) x = read() + 1,y = read() + 1,z = read(),add(x,y,z),add(y,x,z); dfs(1,1); printf("%lld\n",min(dp[1][0],dp[1][1])); return 0; }
当你意识到,每个上一秒都成为永恒。