题意:树上最长不相交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 }
解:带权二分/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 }
本题只要整数二分就行了。
负数二分用右移,是向下取整。
细节:可能最优点不是凸包上的顶点,是一条边中间。我们这时找到靠左的那个顶点,然后用这个斜率 * 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 }