逐个击破

传送门

一道很好的树形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;
}

 

posted @ 2018-11-08 17:17  CaptainLi  阅读(322)  评论(0编辑  收藏  举报