[关闭]
@dxbdly 2022-12-03T08:23:15.000000Z 字数 8432 阅读 442

2022.11.30 杭州集训Day2 考试记录

2022秋 2022杭州集训


考场得分:

T1 T2 T3 Sum
100 60 0 160

T1 Max Tree

考点:树形DP

用时: to

得分:

Description

给定一棵 个点的树,点有点权,定义路径的权值为路径上点的权值和,请在其中选择 个点 ,使得两两之间的路径权值和最大。

Solution

签到题,但调了很久。

树形背包再也不要滚动了!!!

统计一堆路径点权和,容易想到拆成每个点对答案的贡献,也就是这个点被经过了几次。

那么设: 表示 这棵子树中,选了 个点,根选或不选。

转移可以随便转移,注意树形背包的复杂度别写假,注意别滚动。

Summary

树形背包再也不要滚动了!!!

Code

  1. //The Code Is From Dawn
  2. #include<bits/stdc++.h>
  3. #define int long long
  4. using namespace std;
  5. inline int read() {
  6. int x = 0;
  7. char c = getchar();
  8. bool f = 0;
  9. while(!isdigit(c)) f |= (c == '-'), c = getchar();
  10. while(isdigit(c)) x = (x * 10) + (c ^ 48), c = getchar();
  11. return f ? -x : x;
  12. }
  13. inline void write(int x){
  14. if(x<0){putchar('-');x=-x;}
  15. if(x>9)write(x/10);
  16. putchar(x%10+'0');
  17. }
  18. inline void writeln(int x){write(x); puts("");}
  19. inline void writepl(int x){write(x); putchar(' ');}
  20. const int maxn = 4005, INF = 1e18 + 7;
  21. int n, K;
  22. int a[maxn];
  23. struct node {
  24. int v, nex;
  25. }edge[maxn << 1];
  26. int head[maxn], len, Siz[maxn], f[maxn][maxn][2], Cnt[maxn][2];
  27. inline void make_map(int u, int v) {
  28. len++;
  29. edge[len].nex = head[u];
  30. edge[len].v = v;
  31. head[u] = len;
  32. }
  33. inline void Search(int x, int fa) {
  34. Siz[x] = 1, f[x][0][0] = f[x][1][1] = 0;
  35. for(register int i = head[x]; i; i = edge[i].nex) {
  36. int y = edge[i].v;
  37. if(y == fa) continue;
  38. Search(y, x);
  39. }
  40. for(register int i = head[x]; i; i = edge[i].nex) {
  41. int y = edge[i].v;
  42. if(y == fa) continue;
  43. for(register int j = 0; j <= min(K, Siz[x] + Siz[y]); ++j) Cnt[j][0] = Cnt[j][1] = -INF;
  44. for(register int j = 0; j <= min(K, Siz[x]); ++j)
  45. for(register int k = 0; k <= min(K - j, Siz[y]); ++k)
  46. for(register int op = 0; op < 2; ++op) Cnt[j + k][op] = max(Cnt[j + k][op], f[x][j][op] + f[y][k][0] + j * k * a[x]);
  47. Siz[x] += Siz[y];
  48. for(register int j = 0; j <= min(K, Siz[x]); ++j)
  49. for(register int op = 0; op < 2; ++op) f[x][j][op] = Cnt[j][op];
  50. }
  51. for(register int i = 0; i <= min(K, Siz[x]); ++i) {
  52. for(register int op = 0; op < 2; ++op) f[x][i][op] += i * (K - i) * a[x];
  53. f[x][i][0] = max(f[x][i][0], f[x][i][1]);
  54. }
  55. }
  56. signed main() {
  57. freopen("tree.in", "r", stdin);
  58. freopen("tree.out", "w", stdout);
  59. n = read(), K = read();
  60. for(register int i = 1; i <= n; ++i) a[i] = read();
  61. for(register int i = 1; i <= n; ++i) {
  62. for(register int j = 0; j <= K; ++j) f[i][j][0] = f[i][j][1] = -INF;
  63. f[i][0][0] = f[i][1][1] = 0;
  64. }
  65. for(register int i = 1; i < n; ++i) {
  66. int u = read(), v = read();
  67. make_map(u, v), make_map(v, u);
  68. }
  69. Search(1, 0);
  70. printf("%lld\n", f[1][K][0]);
  71. return 0;
  72. }

T2 排列

考点:DP,观察性质

用时: to

Description

给定一个 的矩阵 ,你需要构造出一个排列 ,定义它的生成区间集合 ,生成序列 ,价值为 ,最大化价值。

Solution

问题最大的一道题,不应该没做出来。

一开始就想到了差分,发现他每次从第 行转到 第 行只能往左下,正下,右下三个位置走。

考虑 DP,但是发现有一些不合法的走法,想了很久。

后来二又告诉我这个做法是能做的,但是要把状态改一改,计入一个提前量,不是很会。

我们舍弃差分,但是仍然用到推出来的一些性质。

我们还是DP,考虑到这个生成序列由很多线段组成,我们尝试往这些线段加点,我们称一个线段封闭,代表他不再加点。

我们设: 表示前 个点,有 个未封闭的线段的答案,四种转移:

个操作需要有未封闭的线段。

Summary

一直在想那个差分,没想到可以直接DP

Code

  1. //The Code Is From Dawn
  2. #include<bits/stdc++.h>
  3. #define int long long
  4. using namespace std;
  5. inline int read() {
  6. int x = 0;
  7. char c = getchar();
  8. bool f = 0;
  9. while(!isdigit(c)) f |= (c == '-'), c = getchar();
  10. while(isdigit(c)) x = (x * 10) + (c ^ 48), c = getchar();
  11. return f ? -x : x;
  12. }
  13. const int maxn = 3005, INF = 1e18 + 7;
  14. int n;
  15. int a[maxn][maxn], f[maxn][maxn], Answer = -INF;
  16. inline int Search(int x, int y) {
  17. if(f[x][y] != -1) return f[x][y];
  18. if(y < 1) return -INF;
  19. if(x > n && y > 1) return -INF;
  20. if(x > n && y <= 1) return 0;
  21. int Cnt = -INF;
  22. Cnt = max(Cnt, a[x][y] + Search(x + 1, y));
  23. Cnt = max(Cnt, a[x][y] + Search(x + 1, y - 1));
  24. if(y > 1) Cnt = max(Cnt, a[x][y + 1] + Search(x + 1, y));
  25. Cnt = max(Cnt, a[x][y + 1] + Search(x + 1, y + 1));
  26. return f[x][y] = Cnt;
  27. }
  28. signed main() {
  29. freopen("permutation.in", "r", stdin);
  30. freopen("permutation.out", "w", stdout);
  31. n = read();
  32. for(register int i = 1; i <= n; ++i)
  33. for(register int j = 1; j <= n; ++j) a[i][j] = read();
  34. memset(f, -1, sizeof(f));
  35. printf("%lld\n", Search(1, 1));
  36. return 0;
  37. }

T3 圆

考点:平面图,分治,三角剖分,最短路,双指针

用时:

得分:

Description

给定一张 个点的环图,边有边权,还有 条额外边,也有边权。保证图是一张平面图。找到所有点对之间的最短路之和。

Solution

毒瘤分治题。

统计两两之间的答案,考虑分治。

我们考虑取一条边的两个点做分治中心,拿这条边将图割成两半,两边单独计算,我们现在只考虑跨过中线的答案。

由于是一个平面图,所以这两半的点之间的最短路一定经过这两个分治中心之一。

令分治中心为 ,一组跨过两半的点对为 则 它们的最短路为

其中 可以直接以 暴力跑最短路得到。

这个贡献两边都与 两点有关,我们套路的希望将他们分开。

我们假设:

则有:

我们将每个点按照这个关键字从大到小排序。

也就是说,对于每个 ,我们考虑他前面和后面有多少个与他在不同半区的 ,假设后面有 个,共有 个,那么 的贡献为

那么双指针扫一遍就行,接着就是分治下去。

但我们发现直接分治下去是不行的,因为有可能,同一个半区的两点间距离仍跨过了 这样的最短距离你就统计不到了。

怎么办呢,我们考虑把 这条边拆成两条,分给左部和右部,并把这条边的距离改成另一个半圈的距离。

然后我们就要考虑如何选取分治中心,以及如何保证复杂度了。

理论上分治中心怎么选算的答案都是对的,但为了降低复杂度,我们希望把两边的点分得尽量平均,即我们选取 最小的。

但这样还不够,我们需要进行一个神奇的操作——三角剖分。

这样是为了保证,我们找到的这个分治中心,他一定能找到一对点,满足 ,以保证复杂度正确。

复杂度证明可参考 hzk的blog。(懒得写了)

然后来简述一下三角剖分的大概过程。

我们找到所有度数为 的点,注意在这里度数只记录非环边,把他们丢进一个队列中。

我们建一个链表,保存每个点的前驱与后继,每次拿出一个点,将其弹出,若他的前驱后继没非环边,则连一条长度为 的边,否则将前驱后继的度数都减 ,重新检查是否度数为 ,若为 则加入队列。

这样做之后每个点都能在一个三角形内,这样无论怎么划分,都可以保证子集为很多个三角形组成,以保证复杂度。

Summary

分治的思想很经典,三角剖分完全没听过,贡献的计算可以积累。

Code

  1. //The Code Is From Dawn
  2. #include<bits/stdc++.h>
  3. #define int long long
  4. using namespace std;
  5. inline int read() {
  6. int x = 0;0
  7. char c = getchar();
  8. bool f = 0;
  9. while(!isdigit(c)) f |= (c == '-'), c = getchar();
  10. while(isdigit(c)) x = (x * 10) + (c ^ 48), c = getchar();
  11. return f ? -x : x;
  12. }
  13. const int maxn = 2 * 1e5 + 5, INF = 1e18 + 7;
  14. int n, m;
  15. int Val[maxn], Du[maxn], L[maxn], R[maxn];
  16. struct node {
  17. int u, v, w;
  18. };
  19. namespace Init {
  20. vector <node> E;
  21. vector <int> V;
  22. set <int> Point[maxn];
  23. inline void Trangle() {
  24. queue <int> Q;
  25. for(register int i = 1; i <= n; ++i) L[i] = i - 1, R[i] = i % n + 1;
  26. L[1] = n;
  27. for(register int i = 1; i <= n; ++i)
  28. if(!Du[i]) Q.emplace(i);
  29. while(!Q.empty()) {
  30. int x = Q.front(); Q.pop();
  31. int Left = L[x], Right = R[x];
  32. L[Right] = Left, R[Left] = Right;
  33. if(Left == Right || Right % n + 1 == Left) continue;
  34. if(!Point[Left].count(Right)) {
  35. Point[Left].insert(Right);
  36. E.emplace_back((node){Left, Right, INF});
  37. }
  38. else {
  39. Du[Left]--, Du[Right]--;
  40. if(!Du[Left]) Q.emplace(Left);
  41. if(!Du[Right]) Q.emplace(Right);
  42. }
  43. }
  44. }
  45. inline void Work() {
  46. n = read(), m = read();
  47. for(register int i = 1; i <= n; ++i) Val[i] = read();
  48. for(register int i = 1; i <= n; ++i) {
  49. int u = i, v = i % n + 1;
  50. E.emplace_back((node){u, v, Val[i]});
  51. }
  52. for(register int i = 1; i <= m; ++i) {
  53. int u = read(), v = read(), w = read();
  54. Point[u].emplace(v), Point[v].emplace(u);
  55. Du[u]++, Du[v]++, E.emplace_back((node){u, v, w});
  56. }
  57. Trangle();
  58. for(register int i = 1; i <= n; ++i) V.emplace_back(i);
  59. }
  60. }
  61. namespace Divide {
  62. vector < pair<int, int> > G[maxn];
  63. int Pos[maxn], Col[maxn], Dis1[maxn], Dis2[maxn], Vst[maxn], Sum[2], Num[2];
  64. inline bool cmp(int x, int y) { return Dis1[x] - Dis2[x] < Dis1[y] - Dis2[y]; }
  65. inline void Constr(vector <int> V, vector <node> E) {
  66. for(int i : V) G[i].clear();
  67. for(node i : E) G[i.u].emplace_back(make_pair(i.v, i.w)), G[i.v].emplace_back(make_pair(i.u, i.w));
  68. }
  69. inline void Dijstra(int S, int Dist[], vector <int> V) {
  70. priority_queue < pair<int, int> > Q;
  71. for(int i : V) Dist[i] = INF, Vst[i] = 0;
  72. Q.emplace(make_pair(0, S)), Dist[S] = 0;
  73. while(!Q.empty()) {
  74. int x = Q.top().second; Q.pop();
  75. if(Vst[x]) continue;
  76. Vst[x] = 1;
  77. for(pair<int, int> i : G[x]) {
  78. int y = i.first, z = i.second;
  79. if(Dist[y] > Dist[x] + z) {
  80. Dist[y] = Dist[x] + z;
  81. Q.emplace(make_pair(-Dist[y], y));
  82. }
  83. }
  84. }
  85. }
  86. inline int Solve(vector <int> V, vector <node> E) {
  87. if(V.size() == 1) return 0;
  88. if(V.size() == 2) return E[0].w;
  89. if(V.size() == 3) {
  90. int Answer = 0;
  91. Answer += min(E[0].w, E[1].w + E[2].w);
  92. Answer += min(E[1].w, E[0].w + E[2].w);
  93. Answer += min(E[2].w, E[0].w + E[1].w);
  94. return Answer;
  95. }
  96. int Answer = 0, maxx = -INF;
  97. node Mid = (node){0, 0, 0};
  98. for(register int i = 0; i < V.size(); ++i) Pos[V[i]] = i;
  99. for(node i : E) {
  100. int u = Pos[i.u], v = Pos[i.v];
  101. if(v < u) swap(u, v);
  102. int Cnt = min(v - u, (int)V.size() - (v - u));
  103. if(Cnt > maxx) maxx = Cnt, Mid = i;
  104. }
  105. if(Pos[Mid.u] > Pos[Mid.v]) swap(Mid.u, Mid.v);
  106. for(int i : V) Col[i] = 0;
  107. Col[Mid.u] = Col[Mid.v] = 2;
  108. for(register int i = Pos[Mid.u] + 1; i < Pos[Mid.v]; ++i) Col[V[i]] = 1;
  109. Constr(V, E), Dijstra(Mid.u, Dis1, V), Dijstra(Mid.v, Dis2, V);
  110. vector <int> CntV = V;
  111. sort(V.begin(), V.end(), cmp);
  112. memset(Sum, 0, sizeof(Sum)), memset(Num, 0, sizeof(Num));
  113. for(int i : V) if(Col[i] < 2) Sum[Col[i]]++;
  114. for(register int i = 0, j = V.size() - 1; i < V.size(); ++i) {
  115. while(j >= 0 && Dis1[V[i]] + Dis1[V[j]] >= Dis2[V[i]] + Dis2[V[j]]) {
  116. if(Col[V[j]] < 2) Num[Col[V[j]]]++;
  117. j--;
  118. }
  119. int op = (Col[V[i]] ^ 1);
  120. if(Col[V[i]] < 2) Answer += Dis1[V[i]] * (Sum[op] - Num[op]) + Dis2[V[i]] * Num[op];
  121. }
  122. V = CntV;
  123. vector <int> LV, RV;
  124. vector <node> LE, RE;
  125. for(auto &i : E) {
  126. if(i.u == Mid.u) i.w = Dis1[i.v];
  127. if(i.u == Mid.v) i.w = Dis2[i.v];
  128. if(i.v == Mid.u) i.w = Dis1[i.u];
  129. if(i.v == Mid.v) i.w = Dis2[i.u];
  130. }
  131. for(int i : V) {
  132. if(Col[i] == 0 || Col[i] == 2) LV.emplace_back(i);
  133. if(Col[i] == 1 || Col[i] == 2) RV.emplace_back(i);
  134. }
  135. for(node i : E) {
  136. if((Col[i.u] == 0 || Col[i.u] == 2) && (Col[i.v] == 0 || Col[i.v] == 2)) LE.emplace_back(i);
  137. if((Col[i.u] == 1 || Col[i.u] == 2) && (Col[i.v] == 1 || Col[i.v] == 2)) RE.emplace_back(i);
  138. }
  139. return Answer - Dis1[Mid.v] + Solve(LV, LE) + Solve(RV, RE);
  140. }
  141. inline void Work() {
  142. printf("%lld\n", Solve(Init::V, Init::E));
  143. }
  144. }
  145. signed main() {
  146. freopen("circle.in", "r", stdin);
  147. freopen("circle.out", "w", stdout);
  148. Init::Work(), Divide::Work();
  149. return 0;
  150. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注