洛谷P4383 林克卡特树

题意:树上最长不相交k条链。

 1 #include <cstdio>
 2 #include <algorithm>
 3 #include <cstring>
 4 
 5 typedef long long LL;
 6 const int N = 300010;
 7 
 8 struct Edge {
 9     int nex, v;
10     LL len;
11 }edge[N << 1]; int top;
12 
13 LL f[N][110][3];
14 int e[N], n, k, siz[N];
15 
16 inline void add(int x, int y, LL z) {
17     top++;
18     edge[top].v = y;
19     edge[top].len = z;
20     edge[top].nex = e[x];
21     e[x] = top;
22     return;
23 }
24 
25 inline void exmax(LL &a, LL b) {
26     if(a < b) {
27         a = b;
28     }
29     return;
30 }
31 
32 void DFS(int x, int fa) {
33     siz[x] = 1;
34     f[x][0][0] = 0;
35     for(int i = e[x]; i; i = edge[i].nex) {
36         int y = edge[i].v;
37         if(y == fa) {
38             continue;
39         }
40         DFS(y, x);
41         siz[x] += siz[y];
42         // DP
43         for(int j = std::min(k, siz[x]); j >= 1; j--) {
44             for(int p = 0; p <= j && p <= siz[y]; p++) { // p in son
45                 exmax(f[x][j][2], f[x][j - p + 1][1] + f[y][p][1] + edge[i].len);
46                 exmax(f[x][j][0], f[x][j - p][0] + std::max(f[y][p][2], std::max(f[y][p][0], f[y][p][1])));
47                 exmax(f[x][j][1], f[x][j - p][1] + std::max(f[y][p][2], std::max(f[y][p][0], f[y][p][1])));
48                 exmax(f[x][j][2], f[x][j - p][2] + std::max(f[y][p][2], std::max(f[y][p][0], f[y][p][1])));
49                 exmax(f[x][j][1], f[x][j - p][0] + f[y][p][1] + edge[i].len);
50             }
51         }
52     }
53     for(int i = 1; i <= k && i <= siz[x]; i++) {
54         exmax(f[x][i][1], f[x][i - 1][0]);
55     }
56     /*printf("x = %d \n", x);
57     for(int i = 0; i <= k && i <= siz[x]; i++) {
58         printf("%d ||  0 : %lld  1 : %lld  2 : %lld \n", i, f[x][i][0], f[x][i][1], f[x][i][2]);
59     }*/
60     return;
61 }
62 
63 int main() {
64     int n;
65     memset(f, ~0x3f, sizeof(f));
66     scanf("%d%d", &n, &k);
67     k++;
68     int x, y;
69     LL z;
70     for(int i = 1; i < n; i++) {
71         scanf("%d%d%lld", &x, &y, &z);
72         add(x, y, z);
73         add(y, x, z);
74     }
75     DFS(1, 0);
76     printf("%lld\n", std::max(std::max(f[1][k][0], f[1][k][1]), f[1][k][2]));
77     return 0;
78 }
60分DP

 解:带权二分/wqs二分/DP凸优化。

一个比较常见的套路吧。

一般是求解有k限制的最优化问题。且随着k的变化,极值函数上凸/下凸。

这时我们二分一个斜率去切它,会有一个斜率切到我们要的k。

感性理解一下,我们给这k个事物附上权值,然后权值增加的时候k就会变多,权值减小(可以为负)的时候k会变少。

然后会有某个权值使得不限制k时的最优值恰好选了k。这时候我们减去附加的权值即可。

本题就是给每条链加上一个权值。

还有一道题是k条白边,剩下的选黑边的最小生成树。

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 #include <cmath>
 5 
 6 typedef long long LL;
 7 const int N = 300010;
 8 const LL INF = 1e17;
 9 
10 struct Edge {
11     int nex, v;
12     LL len;
13 }edge[N << 1]; int top;
14 
15 LL f[N][3], D;
16 int g[N][3], n, k, e[N];
17 
18 inline void add(int x, int y, LL z) {
19     top++;
20     edge[top].v = y;
21     edge[top].len = z;
22     edge[top].nex = e[x];
23     e[x] = top;
24     return;
25 }
26 
27 inline void exmax(LL &a, int &c, LL b, int d) {
28     if(a < b || (a == b && c > d)) {
29         a = b;
30         c = d;
31     }
32     return;
33 }
34 
35 void DFS(int x, int fa) {
36     f[x][0] = 0; // 初始化 不选链
37     for(int i = e[x]; i; i = edge[i].nex) {
38         int y = edge[i].v;
39         if(y == fa) {
40             continue;
41         }
42         DFS(y, x);
43         exmax(f[x][2], g[x][2], f[x][2] + f[y][2], g[x][2] + g[y][2]);
44         exmax(f[x][2], g[x][2], f[x][1] + f[y][1] + edge[i].len - D, g[x][1] + g[y][1] - 1); // 1 -> 2
45 
46         exmax(f[x][1], g[x][1], f[x][1] + f[y][2], g[x][1] + g[y][2]);
47         exmax(f[x][1], g[x][1], f[x][0] + f[y][1] + edge[i].len, g[x][0] + g[y][1]); // 0 -> 1
48 
49         exmax(f[x][0], g[x][0], f[x][0] + f[y][2], g[x][0] + g[y][2]);
50     }
51     exmax(f[x][1], g[x][1], f[x][0] + D, g[x][0] + 1); // 自己单独开链
52     exmax(f[x][2], g[x][2], f[x][1], g[x][1]);
53     exmax(f[x][2], g[x][2], f[x][0], g[x][0]);
54     return;
55 }
56 
57 inline bool check(LL mid) {
58     D = mid;
59     memset(g, 0, sizeof(g));
60     memset(f, ~0x3f, sizeof(f));
61     DFS(1, 0);
62     //printf("D = %lld \nf = %lld  g = %d \n\n", D, f[1][2], g[1][2]);
63     return 0;
64 }
65 
66 int main() {
67 
68     scanf("%d%d", &n, &k);
69     LL z, r = 10, l;
70     k++;
71     for(int i = 1, x, y; i < n; i++) {
72         scanf("%d%d%lld", &x, &y, &z);
73         add(x, y, z);
74         add(y, x, z);
75         r += std::abs(z);
76     }
77     l = -r;
78     while(l < r) {
79         LL mid = (l + r + 1) >> 1;
80         //printf("%lld %lld  mid = %lld  \n", l, r, mid);
81         check(mid);
82         if(g[1][2] == k) {
83             printf("%lld\n", f[1][2] - k * mid);
84             return 0;
85         }
86         if(g[1][2] > k) {
87             r = mid - 1;
88         }
89         else {
90             l = mid;
91         }
92     }
93 
94     check(r);
95     printf("%lld\n", f[1][2] - k * r);
96     return 0;
97 }
AC代码

本题只要整数二分就行了。

负数二分用右移,是向下取整。

细节:可能最优点不是凸包上的顶点,是一条边中间。我们这时找到靠左的那个顶点,然后用这个斜率 * k就行了。

实数版:

  1 #include <cstdio>
  2 #include <cstring>
  3 #include <algorithm>
  4 #include <cmath>
  5 
  6 typedef long long LL;
  7 const int N = 300010;
  8 const LL INF = 1e17;
  9 const double eps = 1e-6;
 10 
 11 struct Edge {
 12     int nex, v;
 13     LL len;
 14 }edge[N << 1]; int top;
 15 
 16 double f[N][3], D;
 17 int g[N][3], n, k, e[N];
 18 
 19 inline void add(int x, int y, LL z) {
 20     top++;
 21     edge[top].v = y;
 22     edge[top].len = z;
 23     edge[top].nex = e[x];
 24     e[x] = top;
 25     return;
 26 }
 27 
 28 inline void exmax(double &a, int &c, double b, int d) {
 29     // if(a < b || (a == b && c > d)) {
 30     if(a < b) {
 31         a = b;
 32         c = d;
 33     }
 34     return;
 35 }
 36 
 37 void DFS(int x, int fa) {
 38     f[x][0] = 0; // 初始化 不选链
 39     for(int i = e[x]; i; i = edge[i].nex) {
 40         int y = edge[i].v;
 41         if(y == fa) {
 42             continue;
 43         }
 44         DFS(y, x);
 45         exmax(f[x][2], g[x][2], f[x][2] + f[y][2], g[x][2] + g[y][2]);
 46         exmax(f[x][2], g[x][2], f[x][1] + f[y][1] + edge[i].len - D, g[x][1] + g[y][1] - 1); // 1 -> 2
 47 
 48         exmax(f[x][1], g[x][1], f[x][1] + f[y][2], g[x][1] + g[y][2]);
 49         exmax(f[x][1], g[x][1], f[x][0] + f[y][1] + edge[i].len, g[x][0] + g[y][1]); // 0 -> 1
 50 
 51         exmax(f[x][0], g[x][0], f[x][0] + f[y][2], g[x][0] + g[y][2]);
 52     }
 53     exmax(f[x][1], g[x][1], f[x][0] + D, g[x][0] + 1); // 自己单独开链
 54     exmax(f[x][2], g[x][2], f[x][1], g[x][1]);
 55     exmax(f[x][2], g[x][2], f[x][0], g[x][0]);
 56     return;
 57 }
 58 
 59 inline bool check(double mid) {
 60     D = mid;
 61     memset(g, 0, sizeof(g));
 62     //memset(f, ~0x3f, sizeof(f));
 63     for(int i = 1; i <= n; i++) {
 64         f[i][0] = f[i][1] = f[i][2] = -INF;
 65     }
 66     DFS(1, 0);
 67     //printf("D = %lld \nf = %lld  g = %d \n\n", D, f[1][2], g[1][2]);
 68     return 0;
 69 }
 70 
 71 int main() {
 72 
 73     scanf("%d%d", &n, &k);
 74     LL z;
 75     double r = 10, l;
 76     k++;
 77     for(int i = 1, x, y; i < n; i++) {
 78         scanf("%d%d%lld", &x, &y, &z);
 79         add(x, y, z);
 80         add(y, x, z);
 81         r += std::abs(z);
 82     }
 83     l = -r;
 84     while(fabs(r - l) > eps) {
 85         double mid = (l + r) / 2;
 86         //printf("%lld %lld  mid = %lld  \n", l, r, mid);
 87         check(mid);
 88         if(g[1][2] == k) {
 89             printf("%.0f\n", f[1][2] - k * mid);
 90             return 0;
 91         }
 92         if(g[1][2] > k) {
 93             r = mid;
 94         }
 95         else {
 96             l = mid;
 97         }
 98     }
 99 
100     check(r);
101     printf("%.0f\n", f[1][2] - k * r);
102     return 0;
103 }
AC代码

猜你喜欢

转载自www.cnblogs.com/huyufeifei/p/10396505.html