luogu P2700 逐个击破

传送门

好题啊

给定边权树 求隔离所有指定点的最小花费

考虑树dp的话 自然想到

f[x]表示子树内处理完从根节点出发没有敌人的最小花费 

g[x]表示子树内处理完从根节点出发仍有敌人的最小花费 这个时候仍然合法()

又显然根节点是否有敌人是有影响的 所以分类讨论

 

首先子树没有敌人不用考虑

I. 根节点有敌人的话 f[x]就是inf

g[x]直接取f[v]和g[v]+cst[i]最小值

表示是否切x->v这条边

II. 如果根节点没有 

那么g[x]维护的就是选择一个花费最小的儿子切

而这样的花费就是切掉其他所有儿子的花费加上这个的g[v]

所以我们回溯更新f[]和g[]之前先处理出子树是否有敌人和切掉所有儿子的花费

 

方程看代码吧 强烈建议自己手推

注意这题开longlong!!!(我的30min啊..)

还有就是不要忘了初值还有读入有0啥的 

Time cost : 75min

Code:

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<cmath>
 5 #include<queue>
 6 #include<vector>
 7 #define itn int
 8 #define ms(a,b) memset(a,b,sizeof a)
 9 #define rep(i,a,n) for(int i = a;i <= n;i++)
10 #define per(i,n,a) for(int i = n;i >= a;i--)
11 #define inf 1e13
12 using namespace std;
13 typedef long long ll;
14 #define int ll
15 ll read() {
16     ll as = 0,fu = 1;
17     char c = getchar();
18     while(c < '0' || c > '9') {
19         if(c == '-') fu = -1;
20         c = getchar();
21     }
22     while(c >= '0' && c <= '9') {
23         as = as * 10 + c - '0';
24         c = getchar();
25     }
26     return as * fu;
27 }
28 //head
29 const int N = 100003;
30 int head[N],nxt[N<<1],mo[N<<1],cnt;
31 ll cst[N<<1];
32 void _add(int x,int y,ll w) {
33     mo[++cnt] = y;
34     cst[cnt] = w;
35     nxt[cnt] = head[x];
36     head[x] = cnt;
37 }
38 void add(int x,int y,ll w) {if(x^y)_add(x,y,w),_add(y,x,w);}
39 
40 ll w[N];
41 ll f[N],g[N];
42 
43 bool col[N],vis[N];
44 void dfs(int x,int p) {
45     f[x] = g[x] = 0ll,vis[x] = col[x];
46     ll tot = 0ll;
47     for(int i = head[x];i;i = nxt[i]) {
48         int sn = mo[i];
49         if(sn == p) continue;
50         dfs(sn,x),vis[x] |= vis[sn];
51         tot += min(f[sn],g[sn] + cst[i]);
52     }
53     
54     if(col[x]) {
55         f[x] = inf;
56         for(int i = head[x];i;i = nxt[i]) {
57             int sn = mo[i];
58             if(sn == p || !vis[sn]) continue;
59             if(col[sn]) g[x] += g[sn] + cst[i];
60             else g[x] += min(f[sn],g[sn] + cst[i]);
61         }
62         
63     } else {
64         g[x] = tot;
65         for(int i = head[x];i;i = nxt[i]) {
66             int sn = mo[i];
67             if(sn == p || !vis[sn]) continue;
68             g[x] = min(g[x], tot - min(f[sn],g[sn] + cst[i]) + g[sn]);
69             if(col[sn]) f[x] += g[sn] + cst[i];
70             else f[x] += min(f[sn],g[sn] + cst[i]);
71         }
72     }
73 }
74 
75 signed main() {
76     int n = read(),k = read();
77     rep(i,1,k) col[read()+1] = 1;
78     rep(i,2,n) {
79         int x = read() + 1,y = read() + 1;
80         add(x,y,read());
81     }
82     dfs(1,1);
83     printf("%lld\n",min(f[1],g[1]));
84     return 0;
85 }
View Code

 

 

----Update on 11.04-----

一开始考虑的时候是把尽量小的边割开

然后...

然后就没法维护了...只能遍历两侧所有点对O(n^3)

发现一个友军节点只能连一个敌人

连边之后考虑染色 连过敌人的友军和敌人的性质是一样的

所以考虑染色 边权从大到小

连边的时候如果只有一边是友军就染成敌军

两边都是敌军就不连

复杂度排序O(mlgm)?反正不高

话说真正考试的时候还是比较好想的

posted @ 2018-11-03 23:10  白怀潇  阅读(126)  评论(0)    收藏  举报