[关闭]
@Dmaxiya 2026-03-09T03:48:08.000000Z 字数 10874 阅读 17

2018 Multi-University Training Contest 10

暑期集训


链接:2018 Multi-University Training Contest 10
过题数:4
排名:232
成员:官展鹏,孙昊哲

E. TeaTree

题意

一棵 个节点的树,树根为 ,第 个节点权值为 ,对于每个节点,求出以该节点为最近公共祖先的所有节点对权值 的最大值。

输入

第一行为一个整数 ,第二行为 个整数 开始),表示节点 的父节点编号,第三行为 个整数 ,表示每个节点的权值。

输出

输出 行,第 行为题目所求关于节点 的答案。

题解

预处理出 以内所有数字的约数,再将每个节点按权值映射为约数集合;深搜整棵树,每个节点完成搜索后存的是当前节点对应子树的所有节点权值的约数集合,处理到某个节点时,尝试将所有子节点对应的约数集合合并,在合并的过程中计算以当前节点为最近公共祖先的节点对权值最大公约数:遍历其中一个子节点的约数集合,从另一个集合中查找是否存在相同约数,如果存在说明该约数是一个满足条件的约数,取所有遍历结果的最大值就是答案。

最后使用启发式合并可将时间复杂度降低至 ,注意:使用集合合并完成后,需要删除小的集合,否则会 MLE。

过题代码

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define endl '\n'
  4. typedef long long LL;
  5. const int maxn = 100000 + 100;
  6. int n, f, x;
  7. int ans[maxn];
  8. set<int> fac[maxn], node[maxn];
  9. vector<int> G[maxn];
  10. void dfs(int root) {
  11. ans[root] = -1;
  12. for (int pos : G[root]) {
  13. dfs(pos);
  14. if (node[root].size() < node[pos].size()) {
  15. swap(node[root], node[pos]);
  16. }
  17. for (int x : node[pos]) {
  18. if (node[root].find(x) != node[root].end()) {
  19. ans[root] = max(ans[root], x);
  20. }
  21. }
  22. for (int x : node[pos]) {
  23. node[root].insert(x);
  24. }
  25. node[pos].clear();
  26. }
  27. }
  28. int main() {
  29. #ifdef ExRoc
  30. freopen("test.txt", "r", stdin);
  31. #endif // ExRoc
  32. ios::sync_with_stdio(false);
  33. int mx = 0;
  34. for (int i = 1; i < maxn; ++i) {
  35. for (int j = i; j < maxn; j += i) {
  36. fac[j].insert(i);
  37. mx = max(mx, (int) fac[j].size());
  38. }
  39. }
  40. cin >> n;
  41. for (int i = 2; i <= n; ++i) {
  42. cin >> f;
  43. G[f].push_back(i);
  44. }
  45. for (int i = 1; i <= n; ++i) {
  46. cin >> x;
  47. node[i] = fac[x];
  48. }
  49. dfs(1);
  50. for (int i = 1; i <= n; ++i) {
  51. cout << ans[i] << endl;
  52. }
  53. return 0;
  54. }

G. Cyclic

题意

计算满足以下条件的本质不同的全排列的数量:

长度为 ,下标范围和数字范围都是 ,对于任意 都不满足

若一个排列通过循环右移能变成另一个排列,则认为这两个排列本质相同。

输入

第一行为一个整数 表示数据组数,每组数据一个整数 ,含义如题。

输出

对于每组数据输出一行表示方案数对 取余的结果。

题解

首先考虑如何处理本质不同,由于两个排列只要循环移动后完全相同就认为本质相同,那我们可以将第一个数字 固定在第 位,再排列其它数字,这样计算的所有排列都是本质不同的。

完全不包含约束条件 的全排列方案数比较难求,尝试求其补集:至少有一个 满足条件 的方案数,再用全集方案数 减去补集方案数就是答案(这里已固定数字 的位置,所以只有 个数字可排列)。

考虑求至少有一个 满足条件 的方案数,相当于先从 个数字里选一个 绑定(方案数 ),再将剩下能自由排列的 个元素做全排列(方案数 ,注意这里已经将数字 的位置固定),最终方案数为 ;但这种计算又包含了至少 个、至少 个……至少 满足条件 的方案数,因此还需要继续排除之排除,由此考虑到容斥。

同理,要计算至少 满足条件的方案数公式为 ,再使用容斥原理即可得到公式:

时公式会出现 ,考虑其实际含义,即 个数字都满足条件 ,此时只可能是 一种排列,所以定义 可满足题意,可单独提出该项,得到:

过题代码

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define endl '\n'
  4. typedef long long LL;
  5. const int maxn = 100000 + 100;
  6. const LL MOD = 998244353;
  7. int T, n;
  8. LL ans, f;
  9. LL inv[maxn], pro[maxn], invpro[maxn];
  10. void init() {
  11. inv[1] = 1;
  12. for (int i = 2; i < maxn; ++i) {
  13. inv[i] = (LL)(MOD - MOD / i) * inv[MOD % i] % MOD;
  14. }
  15. pro[0] = invpro[0] = 1;
  16. for (int i = 1; i < maxn; ++i) {
  17. pro[i] = pro[i - 1] * i % MOD;
  18. invpro[i] = invpro[i - 1] * inv[i] % MOD;
  19. }
  20. }
  21. LL C(int n, int m) {
  22. if(n < m) {
  23. return 0;
  24. }
  25. return pro[n] * invpro[m] % MOD * invpro[n - m] % MOD;
  26. }
  27. int main() {
  28. #ifdef ExRoc
  29. freopen("test.txt", "r", stdin);
  30. #endif // ExRoc
  31. ios::sync_with_stdio(false);
  32. init();
  33. cin >> T;
  34. while (T--) {
  35. cin >> n;
  36. ans = (n % 2 == 0 ? 1 : -1);
  37. f = 1;
  38. for (int i = 0; i < n; ++i) {
  39. ans += f * C(n, i) * pro[n - i - 1] % MOD;
  40. ans = (ans % MOD + MOD) % MOD;
  41. f *= -1;
  42. }
  43. cout << ans << endl;
  44. }
  45. return 0;
  46. }

H. Pow

题意

个数字分别为 ,要求从中选出一个子集(允许为空),将子集内的所有数字相加,求所有子集内数字相加后的结果的集合共有多少个不同的数字。

输入

第一行为一个整数 ,表示有 组数据,第二行为 ,含义如题。

输出

每组数据输出一行表示答案。

题解

所有子集内数字的和相当于所有三进制数中,每一位为 或者为 的情况,总共有 位,这些数字的值一定都不相等,所以答案为 ,用大数乘法加快速幂可 AC。

过题代码

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define endl '\n'
  4. typedef long long LL;
  5. const int maxn = 500 + 100;
  6. struct BigInteger {
  7. int dig;
  8. LL num[maxn];
  9. BigInteger() {
  10. dig = 0;
  11. memset(num, 0, sizeof(num));
  12. }
  13. };
  14. ostream& operator<<(ostream& cout, const BigInteger& bigInteger) {
  15. for (int i = bigInteger.dig; i >= 0; --i) {
  16. cout << bigInteger.num[i];
  17. }
  18. return cout;
  19. }
  20. BigInteger operator*(const BigInteger &a, const BigInteger &b) {
  21. BigInteger ans;
  22. for (int i = 0; i <= a.dig; ++i) {
  23. for (int j = 0; j <= b.dig; ++j) {
  24. ans.num[i + j] += a.num[i] * b.num[j];
  25. }
  26. }
  27. for (int i = 0; i < maxn - 1; ++i) {
  28. ans.num[i + 1] += ans.num[i] / 10;
  29. ans.num[i] %= 10;
  30. if (ans.num[i] != 0) {
  31. ans.dig = i;
  32. }
  33. }
  34. return ans;
  35. }
  36. BigInteger fastPow(BigInteger res, int n) {
  37. BigInteger ans;
  38. for (ans.num[0] = 1; n != 0; n >>= 1) {
  39. if ((n & 1) == 1) {
  40. ans = ans * res;
  41. }
  42. res = res * res;
  43. }
  44. return ans;
  45. }
  46. int T, n;
  47. BigInteger two;
  48. int main() {
  49. #ifdef ExRoc
  50. freopen("test.txt", "r", stdin);
  51. #endif // ExRoc
  52. ios::sync_with_stdio(false);
  53. two.num[0] = 2;
  54. cin >> T;
  55. while (T--) {
  56. cin >> n;
  57. cout << fastPow(two, n) << endl;
  58. }
  59. return 0;
  60. }

I. Count

题意

给定 ,计算以下公式:

其中当 值为 时表达式 结果为 ,否则为

输入

第一行为一个整数 ,表示有 组数据,每组数据一个整数 ,即所给定 值。

输出

每组数据输出一行表示答案。

题解

可得:

,则原式替换为:

范围内 互质的所有奇数一定都与 互质,证明如下:

  • 如果为偶数则与 的最大公约数为
  • 如果一个奇数与 存在大于 的约数,那么这个约数一定不为 ,且同样是 的约数,剩余的奇数一定同时与 都互质

如果 为奇数,则 范围内与 互质的奇数个数与偶数个数相等,因为 ,如果 与某个奇数 互质,那么 一定也与 互质,而 是奇数,所以 的奇偶性相反,所以满足条件的数字个数为

如果 为偶数,则 范围内所有与 互质的数字一定都是奇数,所以满足条件的数字个数为

其中:

过题代码

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define endl '\n'
  4. typedef long long LL;
  5. const int maxn = 20000000 + 100;
  6. int T, n, cnt;
  7. int prime[maxn], phi[maxn];
  8. bool vis[maxn];
  9. LL ans[maxn];
  10. void init(int n) {
  11. phi[1] = 1;
  12. for(int i = 2; i <= n; ++i) {
  13. if(!vis[i]) {
  14. prime[cnt++] = i;
  15. phi[i] = i - 1;
  16. }
  17. for(int j = 0; j < cnt && i <= n / prime[j]; ++j) {
  18. int k = i * prime[j];
  19. vis[k] = true;
  20. if(i % prime[j] == 0) {
  21. phi[k] = phi[i] * prime[j];
  22. break;
  23. } else {
  24. phi[k] = phi[i] * (prime[j] - 1);
  25. }
  26. }
  27. }
  28. for (int i = 1; i < maxn; ++i) {
  29. if (i % 2 == 0) {
  30. ans[i] = ans[i - 1] + phi[i];
  31. } else {
  32. ans[i] = ans[i - 1] + phi[i] / 2;
  33. }
  34. }
  35. }
  36. int main() {
  37. #ifdef ExRoc
  38. freopen("test.txt", "r", stdin);
  39. #endif // ExRoc
  40. ios::sync_with_stdio(false);
  41. init(maxn - 1);
  42. cin >> T;
  43. while (T--) {
  44. cin >> n;
  45. cout << ans[n] << endl;
  46. }
  47. return 0;
  48. }

I. CSGO

题意

个主武器和 个副武器,每个武器都有一个主属性值 个副属性值 ,你需要从中选择 个主武器和 个副武器,要求选出的武器组合让以下公式值最大(主武器属性用 标记,副武器属性用 标记):

输入

第一行为一个整数 ,表示有 组数据,每组数据第一行为 个整数 ,接下去 行每行 个整数,第一个整数为 ,后面 个整数为 ,继续往后 行格式与数据范围同前 行,表示副武器各项属性值,所有输入含义如题。

数据保证

输出

每组数据输出一行整数表示答案。

题解

最远曼哈顿距离扩展版,仅推导 维最远曼哈顿距离公式,然后再拓展至 维:

发现 可独立求解,且其中 的加减项组合即 的组合,用集合 表示所有 系数组合的集合(即 ),第 个组合为 ,第 个组合中第 个系数为 ,由此可以将上式简写为:

以上公式将 改为 ,再加上 ,就是本题答案:

过题代码

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define endl '\n'
  4. typedef long long LL;
  5. const int maxn = 100 + 100;
  6. int T, n, m, k;
  7. LL s, ans;
  8. LL num[maxn], ansm[maxn], anss[maxn];
  9. void inputAndCalMax(int n, int k, int f, LL *ans) {
  10. fill(ans, ans + maxn, LONG_LONG_MIN);
  11. for (int i = 0; i < n; ++i) {
  12. cin >> s;
  13. for (int j = 0; j < k; ++j) {
  14. cin >> num[j];
  15. }
  16. for (int j = 0; j < (1 << k); ++j) {
  17. LL tmp = s;
  18. for (int kk = 0; kk < k; ++kk) {
  19. if (((j >> kk) & 1) == 1) {
  20. tmp += num[kk] * f;
  21. } else {
  22. tmp -= num[kk] * f;
  23. }
  24. }
  25. ans[j] = max(ans[j], tmp);
  26. }
  27. }
  28. }
  29. int main() {
  30. #ifdef ExRoc
  31. freopen("test.txt", "r", stdin);
  32. #endif // ExRoc
  33. ios::sync_with_stdio(false);
  34. cin >> T;
  35. while (T--) {
  36. cin >> n >> m >> k;
  37. inputAndCalMax(n, k, 1, ansm);
  38. inputAndCalMax(m, k, -1, anss);
  39. ans = 0;
  40. for (int i = 0; i < (1 << k); ++i) {
  41. ans = max(ans, ansm[i] + anss[i]);
  42. }
  43. cout << ans << endl;
  44. }
  45. return 0;
  46. }

L. Videos

题意

两种视频共 个,一天共有 小时,共有 个人观看视频,每个视频只能给一个人看,每个视频的开始时间是 ,结束时间是 ,同一个人观看的视频播放时间不能重叠,例如:视频 的播放时间区间是 ,视频 的播放时间是 ,则这两个视频可以被同一个人观看,但若视频 的播放时间为 ,则视频 不能被同一个人观看。

一个人观看一个视频能获得这个视频对应的幸福指数 ,如果同一个人连续观看两个同类型的视频,则幸福指数会减少 ,例如他按顺序观看的视频类型为: 那么他将减少 幸福指数。

问所有人能获得的幸福指数总和的最大值是多少?

输入

第一行为一个整数 ,表示有 组数据,每组数据第一行为 个整数 ,含义如题,接下去 行每行 个整数 ,表示第 个视频的开始时间、结束时间、幸福指数、类型, 表示第 个视频的类型是 ,否则为

输出

每行输出一个整数表示答案。

题解

最小费用最大流构图,将人作为流量,将幸福指数的相反数作为费用,这样求出来的最小费用的相反数就是最大幸福指数。

  • 将每个视频拆成两个节点:入点与出点,从入点向出点连一条边,容量为 表示只能 个人观看此视频,费用为 ,表示观看此视频能获得的幸福指数;
  • 从超级源点向次级源点连一条边,容量为 费用为 ,用于限制观看总人数;
  • 从次级源点向每个视频的入点连一条边,容量为 ,费用为 ,表示所有人都可以从任意一个视频开始观看;
  • 从每个视频的出点向超级汇点连接一条边,容量为 费用为 ,表示所有人都可从任意一个视频结束观看;
  • 对所有视频进行两两判定,如果同一个人可以先观看视频 再观看视频 (即两个视频的起止时间不重叠),则可以从视频 的出点向视频 的入点连一条边,容量为 ,费用由两个视频的类型决定,如果类型相同则费用为 ,否则为

最后跑一遍最小费用最大流,最小费用的相反数即答案。

过题代码

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define endl '\n'
  4. typedef long long LL;
  5. const int maxn = 400 + 100;
  6. const int maxm = 50000 + 100;
  7. const int INF = 0x3f3f3f3f;
  8. struct Edge {
  9. int to, Next, cap, flow, cost;
  10. Edge() {}
  11. Edge(int t, int n, int ca, int f, int co) {
  12. to = t;
  13. Next = n;
  14. cap = ca;
  15. flow = f;
  16. cost = co;
  17. }
  18. };
  19. struct Node {
  20. int s, t, w, op;
  21. };
  22. int T, n, m, k, w, N, ans;
  23. int head[maxn], ecnt;
  24. int pre[maxn], dis[maxn];
  25. bool inque[maxn];
  26. Edge edge[maxm << 1];
  27. Node node[maxn];
  28. void init(int n) {
  29. N = n;
  30. ecnt = 0;
  31. memset(head, -1, sizeof(int) * N);
  32. }
  33. void addEdge(int u, int v, int cap, int cost) {
  34. edge[ecnt] = Edge(v, head[u], cap, 0, cost);
  35. head[u] = ecnt++;
  36. edge[ecnt] = Edge(u, head[v], 0, 0, -cost);
  37. head[v] = ecnt++;
  38. }
  39. bool spfa(int s, int t) {
  40. queue<int> que;
  41. for(int i = 0; i < N; i++) {
  42. dis[i] = INF;
  43. inque[i] = false;
  44. pre[i] = -1;
  45. }
  46. dis[s] = 0;
  47. inque[s] = true;
  48. que.push(s);
  49. while(!que.empty()) {
  50. int u = que.front();
  51. que.pop();
  52. inque[u] = false;
  53. for(int i = head[u]; i != -1; i = edge[i].Next) {
  54. int v = edge[i].to;
  55. if(edge[i].cap > edge[i].flow && dis[v] > dis[u] + edge[i].cost) {
  56. dis[v] = dis[u] + edge[i].cost;
  57. pre[v] = i;
  58. if(!inque[v]) {
  59. inque[v] = true;
  60. que.push(v);
  61. }
  62. }
  63. }
  64. }
  65. return pre[t] != -1;
  66. }
  67. int minCostMaxflow(int s, int t, int &cost) {
  68. int flow = 0;
  69. cost = 0;
  70. while(spfa(s,t)) {
  71. int Min = INF;
  72. for(int i = pre[t]; i != -1; i = pre[edge[i ^ 1].to]) {
  73. if(Min > edge[i].cap - edge[i].flow) {
  74. Min = edge[i].cap - edge[i].flow;
  75. }
  76. }
  77. for(int i = pre[t]; i != -1; i = pre[edge[i^1].to]) {
  78. edge[i].flow += Min;
  79. edge[i ^ 1].flow -= Min;
  80. cost += edge[i].cost * Min;
  81. }
  82. flow += Min;
  83. }
  84. return flow;
  85. }
  86. int sid() {
  87. return 0;
  88. }
  89. int tid() {
  90. return 2 * m + 1;
  91. }
  92. int midid() {
  93. return 2 * m + 2;
  94. }
  95. int inid(int x) {
  96. return 2 * x - 1;
  97. }
  98. int outid(int x) {
  99. return 2 * x;
  100. }
  101. int main() {
  102. #ifdef ExRoc
  103. freopen("test.txt", "r", stdin);
  104. #endif // ExRoc
  105. ios::sync_with_stdio(false);
  106. cin >> T;
  107. while (T--) {
  108. cin >> n >> m >> k >> w;
  109. init(2 * m + 3);
  110. addEdge(sid(), midid(), k, 0);
  111. for (int i = 1; i <= m; ++i) {
  112. cin >> node[i].s >> node[i].t >> node[i].w >> node[i].op;
  113. addEdge(midid(), inid(i), 1, 0);
  114. addEdge(inid(i), outid(i), 1, -node[i].w);
  115. addEdge(outid(i), tid(), 1, 0);
  116. for (int j = 1; j < i; ++j) {
  117. int ww = (node[i].op == node[j].op ? -w : 0);
  118. if (node[j].t <= node[i].s) {
  119. addEdge(outid(j), inid(i), 1, -ww);
  120. } else if (node[j].s >= node[i].t) {
  121. addEdge(outid(i), inid(j), 1, -ww);
  122. }
  123. }
  124. }
  125. minCostMaxflow(sid(), tid(), ans);
  126. cout << -ans << endl;
  127. }
  128. return 0;
  129. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注