[关闭]
@dxbdly 2022-08-15T13:51:06.000000Z 字数 6024 阅读 226

8-15暑期模拟赛

2022暑


期望分数:

T1 T2 T3 Sum
100 0 100 200

实际分数:

T1 T2 T3 Sum
100 0 0 100

T1 Range

思维难度: ,代码难度:

1.题意

给定一个序列 ,初始全 ,每次操作可选定一个区间 全体加一,求最小次数变为序列

2.解析

法一: 优化暴力

把操作倒过来,考虑减法。

考虑暴力的过程,每次找出一个最小的把当前区间分成两段,并且都减掉这个点剩余的权值。

那么找最小的这个过程考虑离线下来,难处理的就是划分这个区间。

考虑用 把区间的所有端点存一下,每次用树状数组差分修改权值,就维护好了。

法二:差分

很多个区间减的操作,想到差分。

把原数组差分,那么区间的修改就变成了选两个单点,前减后加,由于要都变成

所以显然找前面的一个正的和后面的一个负的配对做操作。

那么双指针扫一遍就好了

给出法一代码:

  1. //The Code Is From Dawn
  2. #include<bits/stdc++.h>
  3. using namespace std;
  4. inline int read() {
  5. int x = 0;
  6. char c = getchar();
  7. bool f = 0;
  8. while(!isdigit(c)) f |= (c == '-'), c = getchar();
  9. while(isdigit(c)) x = (x * 10) + (c ^ 48), c = getchar();
  10. return f ? -x : x;
  11. }
  12. const int maxn = 1e5 + 5;
  13. int n;
  14. int a[maxn], C[maxn], tot;
  15. pair <int, int> ls[maxn], Answer[maxn];
  16. set < pair<int, int> > S;
  17. inline int lowbit(int x) { return x & (-x); }
  18. inline void Update(int x, int num) { while(x <= n) C[x] += num, x += lowbit(x); }
  19. inline int Query(int x) {
  20. int res = 0;
  21. while(x) res += C[x], x -= lowbit(x);
  22. return res;
  23. }
  24. inline void Split(int Val, int Id) {
  25. set< pair<int, int> > :: iterator It = S.upper_bound(make_pair(Id, 0)); It--;
  26. int l = (*It).first; It++; int r = (*It).first, num = Val - Query(Id);
  27. // printf("%d %d %d %d %d\n", Val, Id, l, r, num);
  28. if(num) {
  29. Update(l, num), Update(r + 1, -num);
  30. while(num--) Answer[++tot] = make_pair(l, r);
  31. }
  32. if(Id != l) S.insert(make_pair(Id - 1, 1));
  33. if(Id != r) S.insert(make_pair(Id + 1, 0));
  34. }
  35. int main() {
  36. // freopen("range.in", "r", stdin);
  37. // freopen("range.out", "w", stdout);
  38. n = read();
  39. for(register int i = 1; i <= n; ++i) a[i] = read(), ls[i].first = a[i], ls[i].second = i;
  40. sort(ls + 1, ls + n + 1);
  41. S.insert(make_pair(1, 0)), S.insert(make_pair(n, 1));
  42. for(register int i = 1; i <= n; ++i) Split(ls[i].first, ls[i].second);
  43. printf("%d\n", tot);
  44. for(register int i = 1; i <= tot; ++i) printf("%d %d\n", Answer[i].first, Answer[i].second);
  45. return 0;
  46. }

3.总结

似乎跟大量区间加减有关,但又找不到解决方法的时候差分是很好的尝试。

T2 Count

思维难度:,代码难度

1.题意

给定 ,求合法的 组数,满足:

2.解析

很妙的思维题。

显然 ,考虑容斥。

全集数量很好计算,考虑 的情况。

一个很妙的想法,由于 ,考虑将每个 变成

那么柿子变成:

显然这个柿子和 等价

换句话说,小于 的情况与大于 的情况一一对应,方案数相同。

那么只要考虑 怎么求。

显然可以将 分解为 ,每个质因子相互独立。

对于每个质因子,就是要统计:

一开始觉得这个计数可以直接插板,但是忽略了 的上界。

只能 DP,这个 DP 似乎比较简单,类似于背包选一下值就好了。

补充:后面那个可以插板,枚举钦定 个数不满足上界,然后将总和加上 正常插板就好了,大概是个容斥?

  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 = 1e5 + 5, maxm = 205, mod = 998244353;
  14. int n, m, len;
  15. struct node {
  16. int Val, Num;
  17. }Prime[maxn];
  18. int All, AnsM;
  19. int f[maxm][maxn];
  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 Get_Prime() {
  29. for(register int i = 2; i <= n; ++i) {
  30. if(n % i == 0) Prime[++len].Val = i, Prime[len].Num = 0;
  31. while(n % i == 0) Prime[len].Num++, n /= i;
  32. }
  33. }
  34. inline void Solve_All() {
  35. All = 1;
  36. for(register int i = 1; i <= len; ++i) All = All * (Prime[i].Num + 1) % mod;
  37. All = Ksm(All, m * 2);
  38. }
  39. inline void Calc(int Id) {
  40. f[0][0] = 1;
  41. for(register int i = 1; i <= (m << 1); ++i)
  42. for(register int j = 0; j <= Prime[Id].Num * m; ++j)
  43. for(register int k = 0; k <= min(j, Prime[Id].Num); ++k) f[i][j] = (f[i][j] + f[i - 1][j - k]) % mod;
  44. }
  45. inline void Solve_M() {
  46. AnsM = 1;
  47. for(register int i = 1; i <= len; ++i) {
  48. memset(f, 0, sizeof(f));
  49. Calc(i);
  50. AnsM = (AnsM * f[m << 1][Prime[i].Num * m]) % mod;
  51. }
  52. }
  53. signed main() {
  54. // freopen("count.in", "r", stdin);
  55. // freopen("count.out", "w", stdout);
  56. n = read(), m = read();
  57. Get_Prime(), Solve_All(), Solve_M();
  58. printf("%lld\n", ((All - AnsM + mod) % mod * Ksm(2, mod - 2) % mod + AnsM) % mod);
  59. return 0;
  60. }

3.总结

数论题还是做得不多,没有发现这一美妙的性质

T3 Tree

思维难度:,代码难度:

1.题意:

一颗 个点的树,你将每次从某个节点等概率选择一条边走到相邻节点, 组询问,询问从起点 的期望步数。

2.解析:

好像是一类期望板子题?

一开始的时候一直把状态设成: 表示 的期望步数。

显然最暴力的可以直接列方程组高消。

考虑优化,会发现如果走出了 的贡献是确定的,也就是说只有 上的东西比较重要。

推出了个似乎能树剖维护的东西,大概长这样:

当然还有另一半柿子没推了。

后来听说一个比较好的状态:设 表示 往上走一步的期望, 为往下走一步到 的期望。

这样的好处就是处理路径比较简单,并且转移也很好推,考虑走的这一步的情况就好了,经典期望推法,直接放柿子吧:

把这两个柿子化简:

后面那个求和优化掉:

然后直接把 拆成 做树上前缀和, 做树上前缀和就行了。

  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 = 1e9 + 7;
  14. int n, q;
  15. struct node {
  16. int v, nex;
  17. }edge[maxn];
  18. int head[maxn], len;
  19. int Up[maxn], Siz[maxn], Down[maxn], Du[maxn], Dep[maxn], f[maxn][25];
  20. inline void make_map(int u, int v) {
  21. len++;
  22. edge[len].nex = head[u];
  23. edge[len].v = v;
  24. head[u] = len;
  25. }
  26. inline void Ready1(int x, int fa) {
  27. Up[x] = Du[x];
  28. for(register int i = head[x]; i; i = edge[i].nex) {
  29. int y = edge[i].v;
  30. if(y == fa) continue;
  31. Ready1(y, x);
  32. Up[x] = (Up[x] + Up[y]) % mod;
  33. }
  34. }
  35. inline void Ready2(int x, int fa) {
  36. Down[x] = (Du[fa] + Down[fa]) % mod;
  37. if(x != 1) Down[x] = (Down[x] + Up[fa] - Up[x] + mod - Du[fa] + mod) % mod;
  38. for(register int i = head[x]; i; i = edge[i].nex) {
  39. int y = edge[i].v;
  40. if(y == fa) continue;
  41. Ready2(y, x);
  42. }
  43. }
  44. inline void Deal_First(int x, int fa) {
  45. Dep[x] = Dep[fa] + 1, Up[x] = (Up[x] + Up[fa]) % mod, Down[x] = (Down[x] + Down[fa]) % mod;
  46. for(register int i = 1; i <= 20; ++i)
  47. f[x][i] = f[f[x][i - 1]][i - 1];
  48. for(register int i = head[x]; i; i = edge[i].nex) {
  49. int y = edge[i].v;
  50. if(y == fa) continue;
  51. f[y][0] = x;
  52. Deal_First(y, x);
  53. }
  54. }
  55. inline int Get_Lca(int x, int y) {
  56. if(Dep[x] < Dep[y]) swap(x, y);
  57. for(register int i = 20; i >= 0; --i) {
  58. if(Dep[f[x][i]] >= Dep[y]) x = f[x][i];
  59. if(x == y) return x;
  60. }
  61. for(register int i = 20; i >= 0; --i)
  62. if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
  63. return f[x][0];
  64. }
  65. inline int Calc(int x, int y) {
  66. int Lca = Get_Lca(x, y);
  67. return (Up[x] + Down[y] - Up[Lca] + mod - Down[Lca] + mod) % mod;
  68. }
  69. signed main() {
  70. // freopen("tree.in", "r", stdin);
  71. // freopen("tree.out", "w", stdout);
  72. n = read(), q = read();
  73. for(register int i = 1; i < n; ++i) {
  74. int u = read(), v = read();
  75. make_map(u, v), make_map(v, u), Du[u]++, Du[v]++;
  76. }
  77. Ready1(1, 0), Ready2(1, 0), Deal_First(1, 0);
  78. while(q--) {
  79. int u = read(), v = read();
  80. printf("%lld\n", Calc(u, v));
  81. }
  82. return 0;
  83. }

3.总结

考试时想到了,但是太晚了,主要是积累下这个状态设计。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注