[关闭]
@dxbdly 2022-12-03T01:49:31.000000Z 字数 9432 阅读 312

2022.11.29 杭州集训Day1 考试记录

2022秋 2022杭州集训


得分:

T1 T2 T3 Sum
100 50 0 150

T1

考点:容斥,二项式反演,组合数学

用时: 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. const int maxn = 1e6 + 5, mod = 998244353;
  14. int n, m;
  15. struct node {
  16. int v, nex;
  17. }edge[maxn << 1];
  18. unordered_map <int, int> mapp[maxn];
  19. int head[maxn], len, Du[maxn], Vst[maxn], tot, flag, Cnt1, Cnt2, Answer = 1;
  20. int Frac[maxn], Inv[maxn];
  21. inline void make_map(int u, int v) {
  22. len++;
  23. edge[len].nex = head[u];
  24. edge[len].v = v;
  25. head[u] = len;
  26. }
  27. inline int Ksm(int A, int B) {
  28. int res = 1;
  29. while(B) {
  30. if(B & 1) res = res * A % mod;
  31. A = A * A % mod, B >>= 1;
  32. }
  33. return res;
  34. }
  35. inline void Search(int x, int fa) {
  36. if(Vst[x]) { flag = 1; return ; }
  37. Vst[x] = 1, tot++;
  38. for(register int i = head[x]; i; i = edge[i].nex) {
  39. int y = edge[i].v;
  40. if(y == fa) continue;
  41. Search(y, x);
  42. }
  43. }
  44. inline void Ready() {
  45. Frac[0] = Inv[0] = 1;
  46. for(register int i = 1; i <= n; ++i) Frac[i] = Frac[i - 1] * i % mod;
  47. Inv[n] = Ksm(Frac[n], mod - 2);
  48. for(register int i = n - 1; i >= 1; --i) Inv[i] = Inv[i + 1] * (i + 1) % mod;
  49. }
  50. inline int C(int x, int y) { return Frac[x] * Inv[y] % mod * Inv[x - y] % mod; }
  51. signed main() {
  52. freopen("a.in", "r", stdin);
  53. freopen("a.out", "w", stdout);
  54. n = read(), m = read();
  55. Ready();
  56. for(register int i = 1; i <= m; ++i) {
  57. int u = read(), v = read();
  58. if(!mapp[u][v] && !mapp[v][u]) {
  59. make_map(u, v), make_map(v, u);
  60. Du[u]++, Du[v]++;
  61. }
  62. mapp[u][v] = mapp[v][u] = 1;
  63. }
  64. for(register int i = 1; i <= n; ++i)
  65. if(Du[i] > 2) { printf("0\n"); return 0; }
  66. for(register int i = 1; i <= n; ++i) {
  67. if(!Du[i]) Cnt2++;
  68. else if(!Vst[i]) {
  69. tot = 0, flag = 0, Search(i, 0);
  70. if(tot > 2) {
  71. Answer = (Answer * 2ll) % mod;
  72. if(!flag) Cnt2++;
  73. }
  74. else if(tot == 2) Cnt1++;
  75. }
  76. }
  77. int Sum = 0;
  78. for(register int i = 0; i <= Cnt1; ++i) {
  79. int Cnt = C(Cnt1, i) * Ksm(2ll, Cnt1 - i) % mod * Frac[Cnt1 + Cnt2 - i] % mod;
  80. if(i & 1) Sum = (Sum - Cnt + mod) % mod;
  81. else Sum = (Sum + Cnt) % mod;
  82. }
  83. printf("%lld\n", Answer * Sum % mod);
  84. return 0;
  85. }

T2

考点:线段树,差分

用时: to

得分:

Description

给定 个数的序列,每个元素由三元组 组成。
给定一种操作,每次操作可以选择一段 均相同的区间 将此区间的 均减掉
三种询问,共 次:
询问 :单点修改:
询问 :区间覆盖
询问 :求清空区间 的所有 值的最小代价

Solution

考虑这个操作,明明是区间操作,代价却只跟两个单点有关,这很不好做,我们希望这个代价跟区间中的所有点都挂上钩。

考虑差分,

再考虑一个恒等式:

所以做一段区间的中间一定比端点优,从左往右依次加点,那么能加到当前段就加到当前段,否则再新开一段,则贡献为:

得到 暴力,,考场上止步于此。

我们考虑用线段树维护这个东西。

还是没法在考场上想到如何用线段树维护这种复杂的东西。

我们考虑除了 操作,这是很好维护的。

这个贡献是分子区间来贡献的,类似最大子段和的做法,维护两端的颜色和区间答案。

时我们讨论两段的颜色是否相同,如果相同就把那个区间合并,重新计算贡献。

考虑把 操作加上,他是一个区间推平的操作,我们记 表示 区间 被推平后的答案,把这个东西也扔到线段树上维护即可通过, 非常容易,忽略不讲。

Summary

一道线段树好题,需要对线段树有比较好的理解。

事实证明我还是不会线段树。

Code

  1. //The Code Is From Dawn
  2. #include <bits/stdc++.h>
  3. using namespace std;
  4. const int maxn = 2e5 + 5;
  5. int n, q;
  6. long long a[maxn];
  7. int c[maxn];
  8. long long w[maxn];
  9. struct node {
  10. int l, r;
  11. long long s;
  12. int x, y;
  13. long long ts;
  14. } sum[maxn << 2];
  15. int lazy[maxn << 2];
  16. void pushdown(int l, int r, int rt) {
  17. int mid = l + r >> 1;
  18. if (lazy[rt] != 0) {
  19. sum[rt << 1].l = sum[rt << 1].r = lazy[rt];
  20. sum[rt << 1 | 1].l = sum[rt << 1 | 1].r = lazy[rt];
  21. sum[rt << 1].s = sum[rt << 1].ts;
  22. sum[rt << 1 | 1].s = sum[rt << 1 | 1].ts;
  23. lazy[rt << 1] = lazy[rt], lazy[rt << 1 | 1] = lazy[rt];
  24. lazy[rt] = 0;
  25. }
  26. }
  27. node add(node s1, node s2) {
  28. node t;
  29. t.l = s1.l, t.r = s2.r;
  30. t.s = s1.s + s2.s;
  31. t.ts = s1.ts + s2.ts - a[s1.y] * w[s1.y] - a[s2.x] * w[s2.x] + max(0ll,
  32. a[s1.y] - a[s2.x]) * w[s1.y] + max(0ll, a[s2.x] - a[s1.y]) * w[s2.x];
  33. if (s1.r == s2.l) {
  34. t.s += max(0ll, a[s1.y] - a[s2.x]) * w[s1.y] + max(a[s2.x] - a[s1.y], 0ll) * w[s2.x];
  35. t.s -= a[s1.y] * w[s1.y] + a[s2.x] * w[s2.x];
  36. }
  37. return t;
  38. }
  39. void build(int l, int r, int rt) {
  40. sum[rt].x = l, sum[rt].y = r;
  41. if (l == r)
  42. return sum[rt].l = sum[rt].r = c[l], sum[rt].ts = sum[rt].s = a[l] * 2 * w[l], void();
  43. int mid = l + r >> 1;
  44. build(l, mid, rt << 1);
  45. build(mid + 1, r, rt << 1 | 1);
  46. sum[rt] = add(sum[rt << 1], sum[rt << 1 | 1]);
  47. sum[rt].x = l, sum[rt].y = r;
  48. }
  49. void update(int l, int r, int rt, int x, int y, int k) {
  50. if (x <= l && r <= y) {
  51. sum[rt].l = sum[rt].r = k;
  52. sum[rt].s = sum[rt].ts;
  53. lazy[rt] = k;
  54. return;
  55. }
  56. int mid = l + r >> 1;
  57. pushdown(l, r, rt);
  58. if (x <= mid)
  59. update(l, mid, rt << 1, x, y, k);
  60. if (mid < y)
  61. update(mid + 1, r, rt << 1 | 1, x, y, k);
  62. sum[rt] = add(sum[rt << 1], sum[rt << 1 | 1]);
  63. sum[rt].x = l, sum[rt].y = r;
  64. }
  65. void change(int l, int r, int rt, int x, int k) {
  66. if (l == r)
  67. return sum[rt].s = sum[rt].ts = k * a[l] * 2, void();
  68. int mid = l + r >> 1;
  69. pushdown(l, r, rt);
  70. if (x <= mid)
  71. change(l, mid, rt << 1, x, k);
  72. else
  73. change(mid + 1, r, rt << 1 | 1, x, k);
  74. sum[rt] = add(sum[rt << 1], sum[rt << 1 | 1]);
  75. sum[rt].x = l, sum[rt].y = r;
  76. }
  77. node query(int l, int r, int rt, int x, int y) {
  78. if (x <= l && r <= y)
  79. return sum[rt];
  80. int mid = l + r >> 1;
  81. node s;
  82. pushdown(l, r, rt);
  83. s.l = s.r = s.s = s.x = s.y = s.ts = 0;
  84. s.x = l, s.y = r;
  85. if (x <= mid)
  86. s = query(l, mid, rt << 1, x, y);
  87. if (mid < y) {
  88. if (x <= mid)
  89. s = add(s, query(mid + 1, r, rt << 1 | 1, x, y));
  90. else
  91. s = query(mid + 1, r, rt << 1 | 1, x, y);
  92. }
  93. s.x = l, s.y = r;
  94. return s;
  95. }
  96. signed main() {
  97. freopen("b.in", "r", stdin);
  98. freopen("b.out", "w", stdout);
  99. cin >> n >> q;
  100. for (int i = 1; i <= n; i++)
  101. cin >> a[i] >> c[i] >> w[i];
  102. build(1, n, 1);
  103. for (int i = 1; i <= q; i++) {
  104. int op;
  105. cin >> op;
  106. if (op == 1) {
  107. int x, v1, v2;
  108. cin >> x >> v1 >> v2;
  109. a[x] = v1, w[x] = v2;
  110. change(1, n, 1, x, v2);
  111. }
  112. if (op == 2) {
  113. int l, r, v;
  114. cin >> l >> r >> v;
  115. update(1, n, 1, l, r, v);
  116. }
  117. if (op == 3) {
  118. int l, r;
  119. cin >> l >> r;
  120. cout << query(1, n, 1, l, r).s << endl;
  121. }
  122. }
  123. return 0;
  124. }

T3

考点:图上随机游走,高斯消元,哈希, 容斥,概率生成函数

用时: to

得分:

Description

给定 个字符串 和一个空串 ,每次给 随机接入一个字符,求出 中出现 中所有字符串的期望时间。

Solution

考场上根本没看,一道非常好的题目。

我们先从一些子问题分析起:

暴力情况:JSOI2009有趣的游戏

求概率版本。

我们把所有模式串扔到一个AC自动机上,那么问题变成了一个图上随机游走。

设计DP: 表示状态 出现的概率,

这个转移显然是有环的,所以要高斯消元,复杂度

对暴力的优化:SDOI2017硬币游戏

暴力的瓶颈在于方程太多,我们发现有用的状态只有 个,我们却用了 个方程算了很多无用的东西。

我们考虑把无用的状态缩到一起,统称为 ,这样状态缩减到 ,我们若能找到 个方程即可在 的时间解决问题。

我们考虑从无用的状态 开始,连续的走了某个字符串 ,他结束了的概率。

注意:即使连续的走了某个字符串 ,也不一定在 结束,有可能在走完之前,就已经在某个 结束了,这时后面的概率不需要计算。

我们发现,这里存在一个等量关系。

前者是 出现的概率为

后者是以结束点为角度分类,来表达这个式子。

我们发现提前结束的条件是, 的后缀 正好是 的前缀 ,而 的一段后缀 ,正好是 的剩余前缀 ,这样就会在 提前结束。

也就是说,当 的后缀 等于 的前缀 ,就会造成 的贡献。

所以有:

这样的方程有 个,再加上 ,得到 个方程,可以解决问题。

期望转概率的套路:CTSC2006歌唱王国

给出一个引理:

第一次出现的期望时间等于所有时间从未出现的概率之和

证明需要用到概率生成函数:

我们定义一个形式幂级数 ,它为概率生成函数,则它的系数

他有以下两个性质:

我们设::表示恰好在 的时候停止, 表示第 的时刻还没停止

那么显然有:

将它写成生成函数的形式:

整理一下

求导:

代入:

结论得证。

而所有时间从未出现的概率之和就是上面的

所以:,这里 表示第一次出现

套上 容斥

容易发现,经过上面这么多分析,已经与本题要求的东西很接近了。

我们已经会求第一次出现的期望,而此题要求的是所有都出现的期望。

显然可以套上 容斥, 通过高消来求。

复杂度

Summary

一道非常好的题目,用到了很多题目的思想套路,以及再套一个 容斥,很值得研究。

Code

  1. //The Code Is From Dawn
  2. #include<bits/stdc++.h>
  3. #define int long long
  4. #define ull unsigned long long
  5. using namespace std;
  6. inline int read() {
  7. int x = 0;
  8. char c = getchar();
  9. bool f = 0;
  10. while(!isdigit(c)) f |= (c == '-'), c = getchar();
  11. while(isdigit(c)) x = (x * 10) + (c ^ 48), c = getchar();
  12. return f ? -x : x;
  13. }
  14. const int maxn = 20, maxm = 1e5 + 5, mod = 998244353;
  15. const ull Base = 31;
  16. int n;
  17. int pow26[maxm], len[maxn], flag[maxn], St[maxn], num[maxn][maxn], Val[maxn][maxn];
  18. ull poww[maxm], Sum[maxn][maxm];
  19. char S[maxn][maxm];
  20. inline int Ksm(int A, int B) {
  21. int res = 1;
  22. while(B) {
  23. if(B & 1) res = res * A % mod;
  24. A = A * A % mod, B >>= 1;
  25. }
  26. return res;
  27. }
  28. inline void Ready() {
  29. pow26[0] = 1ll, poww[0] = 1ull;
  30. for(register int i = 1; i <= 1e5; ++i) pow26[i] = pow26[i - 1] * 26ll % mod, poww[i] = poww[i - 1] * Base;
  31. }
  32. inline ull Hash(int i, int l, int r) { return (Sum[i][r] - Sum[i][l - 1]) * poww[100000 - r]; }
  33. inline bool Check(int x, int y) {
  34. if(len[x] < len[y]) return 0;
  35. ull Cnt = Hash(y, 1, len[y]);
  36. for(register int i = 1; i <= len[x] - len[y] + 1; ++i)
  37. if(Cnt == Hash(x, i, i + len[y] - 1)) {
  38. if(len[x] == len[y]) return x < y;
  39. else return 1;
  40. }
  41. return 0;
  42. }
  43. inline int Calc(int x, int y) {
  44. int Cnt = 0;
  45. for(register int i = 1; i <= min(len[x], len[y]); ++i)
  46. if(Hash(x, 1, i) == Hash(y, len[y] - i + 1, len[y])) Cnt = (Cnt + pow26[i]) % mod;
  47. return Cnt;
  48. }
  49. inline int Gause(int x) {
  50. Val[x][x + 1] = 1, Val[x][x] = 0;
  51. for(register int i = 1; i < x; ++i) Val[i][x] = mod - 1, Val[i][x + 1] = 0;
  52. for(register int i = 1; i < x; ++i) Val[x][i] = 1;
  53. for(register int i = 1; i <= x; ++i) {
  54. int Cnt = i;
  55. for(register int j = i; j <= x; ++j)
  56. if(Val[j][i]) { Cnt = j; break; }
  57. swap(Val[i], Val[Cnt]);
  58. Cnt = Ksm(Val[i][i], mod - 2);
  59. for(register int j = i; j <= x + 1; ++j) Val[i][j] = Val[i][j] * Cnt % mod;
  60. for(register int j = i + 1; j <= x; ++j) {
  61. Cnt = mod - Val[j][i];
  62. for(register int k = i; k <= x + 1; ++k) Val[j][k] = (Val[j][k] + Val[i][k] * Cnt % mod) % mod;
  63. }
  64. }
  65. return Val[x][x + 1];
  66. }
  67. inline void Work() {
  68. int Limit = (1 << n), Answer = 0;
  69. for(register int i = 1; i < Limit; ++i) {
  70. int top = 0;
  71. for(register int j = 1; j <= n; ++j)
  72. if((i & (1 << j - 1)) && flag[j]) { top = -1; break; }
  73. if(top == -1) continue;
  74. for(register int j = 1; j <= n; ++j)
  75. if(i & (1 << j - 1)) St[++top] = j;
  76. for(register int j = 1; j <= top; ++j)
  77. for(register int k = 1; k <= top; ++k) Val[j][k] = num[St[j]][St[k]];
  78. int Cnt = __builtin_popcount(i);
  79. if(Cnt & 1) Answer = (Answer + Gause(top + 1)) % mod;
  80. else Answer = (Answer - Gause(top + 1) + mod) % mod;
  81. }
  82. printf("%lld\n", Answer);
  83. }
  84. signed main() {
  85. freopen("c.in", "r", stdin);
  86. freopen("c.out", "w", stdout);
  87. n = read(), Ready();
  88. for(register int i = 1; i <= n; ++i) {
  89. scanf("%s", S[i] + 1);
  90. len[i] = strlen(S[i] + 1);
  91. for(register int j = 1; j <= len[i]; ++j) Sum[i][j] = Sum[i][j - 1] + poww[j] * S[i][j];
  92. }
  93. for(register int i = 1; i <= n; ++i)
  94. for(register int j = 1; j <= n; ++j)
  95. if(i != j && Check(j, i)) { flag[i] = 1; break; }
  96. for(register int i = 1; i <= n; ++i)
  97. for(register int j = 1; j <= n; ++j)
  98. if(!flag[i] && !flag[j]) num[i][j] = Calc(i, j);
  99. Work();
  100. return 0;
  101. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注