• 大三第二周学习笔记


    训练!!!!

    周一

    hdu 5236(概率dp)

    首先要猜测一下策略,策略是将n个字符均分,每敲固定的字符就保存一下。

    比如将n分成i段,那么每一段的长度就是k或k+1,余数部分就分配到每个k,变成k+1

    那么这样就要处理没有保存的情况下,打k个字符的期望

    用dp[k]表示打k个字符的期望,注意这时不是逆推,因为需要的就是正推的。

    那么有dp[i] = dp[i - 1] + p(1 + dp[i]) + (1 - p) 可化为dp[i] = (dp[i - 1] + 1) / (1 - p)

    输出时6位小数即可,我输出了10位反而wa了

    1. #include<bits/stdc++.h>
    2. #define l(k) (k << 1)
    3. #define r(k) (k << 1 | 1)
    4. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    5. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    6. using namespace std;
    7. const int N = 1e5 + 10;
    8. double dp[N], p;
    9. int n, x;
    10. int main()
    11. {
    12. int T, kase = 0;
    13. scanf("%d", &T);
    14. while(T--)
    15. {
    16. scanf("%d%lf%d", &n, &p, &x);
    17. _for(i, 1, n) dp[i] = (dp[i - 1] + 1) / (1 - p);
    18. double ans = 1e18;
    19. _for(i, 1, n)
    20. {
    21. int k = n / i, r = n % i;
    22. ans = min(ans, r * dp[k + 1] + (i - r) * dp[k] + i * x);
    23. }
    24. printf("Case #%d: %.6f\n", ++kase, ans);
    25. }
    26. return 0;
    27. }

    迷宫游戏(期望dp+处理方程)

    首先要写出方程,这个是简单的。注意这道题要求的是走过的边数的期望,而实际上三种情况只有一种情况会增加一条边,所以写方程时不要直接写1

    写出方程后发现有dp[1]和dp[fa[i]]两个未知量,那么就设dp[i] = aidp[1]+bidp[fa[i]],然后代入dp方程中,化成dp[1]和dp[fa[i]]的形式,得到递推式。然后求一下叶子节点的式子即可。

    无解的话就是除0的情况。因为一般情况下是可以算出来的,只有分母为0是不能的。

    1. #include<bits/stdc++.h>
    2. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    3. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    4. using namespace std;
    5. const int N = 1e4 + 10;
    6. double k[N], e[N], a[N], b[N], c[N], t[N];
    7. vector<int> g[N];
    8. int n;
    9. bool dfs(int u, int fa)
    10. {
    11. if(g[u].size() == 1 && g[u][0] == fa)
    12. {
    13. a[u] = k[u];
    14. b[u] = 1 - e[u] - k[u];
    15. c[u] = 1 - e[u] - k[u];
    16. return true;
    17. }
    18. double suma = 0, sumb = 0, sumc = 0;
    19. t[u] = (1 - e[u] - k[u]) / g[u].size();
    20. for(int v: g[u])
    21. {
    22. if(v == fa) continue;
    23. if(!dfs(v, u)) return false;
    24. suma += a[v];
    25. sumb += b[v];
    26. sumc += c[v];
    27. }
    28. if(fabs(1 - t[u] * sumb) < 1e-8) return false;
    29. a[u] = (k[u] + t[u] * suma) / (1 - t[u] * sumb);
    30. b[u] = t[u] / (1 - t[u] * sumb);
    31. c[u] = (t[u] * sumc + 1 - e[u] - k[u]) / (1 - t[u] * sumb);
    32. return true;
    33. }
    34. int main()
    35. {
    36. scanf("%d", &n);
    37. _for(i, 1, n - 1)
    38. {
    39. int u, v;
    40. scanf("%d%d", &u, &v);
    41. g[u].push_back(v);
    42. g[v].push_back(u);
    43. }
    44. _for(i, 1, n)
    45. {
    46. int x, y;
    47. scanf("%d%d", &x, &y);
    48. k[i] = 1.0 * x / 100;
    49. e[i] = 1.0 * y / 100;
    50. }
    51. if(!dfs(1, 0) || fabs(1 - a[1]) < 1e-8) puts("impossible");
    52. else printf("%.10f\n", c[1] / (1 - a[1]));
    53. return 0;
    54. }

    hdu 3535(混合背包)

    这题真的把各种背包结合起来

    强制选一个的操作很秀

    1. #include<bits/stdc++.h>
    2. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    3. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    4. using namespace std;
    5. const int N = 100 + 10;
    6. int dp[N][N], w[N], v[N], n, T, m, s;
    7. int main()
    8. {
    9. while(~scanf("%d%d", &n, &T))
    10. {
    11. memset(dp, 0, sizeof dp);
    12. _for(i, 1, n)
    13. {
    14. scanf("%d%d", &m, &s);
    15. _for(j, 1, m) scanf("%d%d", &w[j], &v[j]);
    16. if(s == 0)
    17. {
    18. _for(j, 0, T) dp[i][j] = -1e9; //因为必须取一个有不合法的状态,所以初始化为负无穷
    19. _for(k, 1, m)
    20. for(int j = T; j >= w[k]; j--)
    21. dp[i][j] = max(dp[i][j], max(dp[i][j - w[k]] + v[k], dp[i - 1][j - w[k]] + v[k])); //强制选一个,以及和前面的状态做背包
    22. }
    23. else if(s == 1)
    24. {
    25. _for(j, 0, T) dp[i][j] = dp[i - 1][j];
    26. _for(k, 1, m)
    27. for(int j = T; j >= w[k]; j--)
    28. dp[i][j] = max(dp[i][j], dp[i - 1][j - w[k]] + v[k]);
    29. }
    30. else
    31. {
    32. _for(j, 0, T) dp[i][j] = dp[i - 1][j];
    33. _for(k, 1, m)
    34. for(int j = T; j >= w[k]; j--)
    35. dp[i][j] = max(dp[i][j], dp[i][j - w[k]] + v[k]);
    36. }
    37. }
    38. printf("%d\n", (dp[n][T] < 0) ? -1: dp[n][T]);
    39. }
    40. return 0;
    41. }

    周二

    D. 2+ doors(位运算+贪心)

    现在多刷刷cf的题,关键是学解题技巧。

    首先是位运算的题很常见的思路就是每一位独立出来考虑,这道题也是一样

    首先若边为0,那么肯定端点都是0,这是可以确定的,赋值。

    然后考虑什么时候字典序最小,那么就从前往后,每一位看能不能为0

    必须为1只有一种情况,就是边为1,而另一个端点为0。对于当前点,前面的点在已经确定了,为了尽量使得当前可以为1,我们可以把后面的位先全部设为1(除了零边赋值为0)

    那么就遍历当前点所有的边,看是否有不得不为1的情况,即一边且另一个端点是0。这样一位一位即可。有一个细节就是可能自己向自己连边,这个时候是一边的话那这一位就肯定为1了。

    1. #include<bits/stdc++.h>
    2. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    3. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    4. using namespace std;
    5. const int N = 2e5 + 10;
    6. int ans[N], t[N], n, m;
    7. vector<pair<int, int>> g[N];
    8. int main()
    9. {
    10. scanf("%d%d", &n, &m);
    11. while(m--)
    12. {
    13. int i, j, x;
    14. scanf("%d%d%d", &i, &j, &x);
    15. if(i > j) swap(i, j);
    16. g[i].push_back({j, x});
    17. g[j].push_back({i, x});
    18. }
    19. _for(j, 0, 30)
    20. {
    21. _for(i, 1, n) t[i] = 1;
    22. _for(i, 1, n)
    23. for(auto x: g[i])
    24. if(!((x.second >> j) & 1))
    25. t[i] = t[x.first] = 0;
    26. _for(i, 1, n)
    27. {
    28. if(!t[i]) continue;
    29. int flag = 1;
    30. for(auto x: g[i])
    31. if(!t[x.first] || i == x.first)
    32. {
    33. flag = 0;
    34. break;
    35. }
    36. if(flag) t[i] = 0;
    37. }
    38. _for(i, 1, n)
    39. if(t[i])
    40. ans[i] |= 1 << j;
    41. }
    42. _for(i, 1, n) printf("%d ", ans[i]);
    43. return 0;
    44. }

    D. Difference Array(暴力+优化)

    这是一类题,暴力看似不可行,实际上是可行的,加一些小优化,或者是复杂度计算。

    这题的关键就是发现计算的过程中会出现很多0,把0单独考虑可以大大加快速度,这样暴力也能过。

    考虑0的话,维护最后的0的位置,每次计算和排序都考虑后面的数,然后继续维护0的位置。注意可能有当前没有0的情况,要判断一下。

    1. #include<bits/stdc++.h>
    2. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    3. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    4. using namespace std;
    5. const int N = 1e5 + 10;
    6. int a[N], b[N], n;
    7. int main()
    8. {
    9. int T; scanf("%d", &T);
    10. while(T--)
    11. {
    12. scanf("%d", &n);
    13. _for(i, 1, n) scanf("%d", &a[i]);
    14. int pos = 0;
    15. _for(i, 1, n)
    16. if(a[i])
    17. {
    18. pos = i - 1;
    19. break;
    20. }
    21. while(n > 1 && a[n - 1])
    22. {
    23. _for(i, max(pos, 1), n) a[i] = a[i + 1] - a[i];
    24. n--;
    25. sort(a + max(pos, 1), a + n + 1);
    26. if(pos) pos--;
    27. while(pos + 1 <= n && !a[pos + 1]) pos++;
    28. }
    29. printf("%d\n", a[n]);
    30. }
    31. return 0;
    32. }

    C. Qpwoeirut And The City(括号匹配)

    思路挺妙,首先左边问号全部左括号,右边全部右括号一定是一个解

    然后看有无第二个解,那就把中间那里交换一下,也就是最右边的左括号和最左边的右括号,这样是影响最小的,然后看这样是否合法。

    1. #include <bits/stdc++.h>
    2. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    3. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    4. using namespace std;
    5. bool check(string s)
    6. {
    7. int len = s.size(), cnt = 0;
    8. rep(i, 0, len)
    9. {
    10. if(s[i] == '(') cnt++;
    11. else cnt--;
    12. if(cnt < 0) return false;
    13. }
    14. return true;
    15. }
    16. int main()
    17. {
    18. int T; scanf("%d", &T);
    19. while(T--)
    20. {
    21. string s;
    22. cin >> s;
    23. int len = s.size();
    24. vector<int> pos;
    25. int l = len / 2, r = len / 2;
    26. rep(i, 0, len)
    27. {
    28. if(s[i] == '(') l--;
    29. else if(s[i] == ')') r--;
    30. else pos.push_back(i);
    31. }
    32. rep(i, 0, l) s[pos[i]] = '(';
    33. rep(i, l, pos.size()) s[pos[i]] = ')';
    34. int ans = 0;
    35. if(l && r)
    36. {
    37. swap(s[pos[l - 1]], s[pos[l]]);
    38. if(check(s)) ans = 1;
    39. }
    40. puts(ans ? "NO" : "YES");
    41. }
    42. return 0;
    43. }

    C. Qpwoeirut And The City(细节)

    这题不难,但写的时候写错了一个细节,调了很久才发现

    1. #include <bits/stdc++.h>
    2. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    3. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    4. using namespace std;
    5. typedef long long ll;
    6. const int N = 1e5 + 10;
    7. ll h[N], a[N];
    8. int n;
    9. int main()
    10. {
    11. int T; scanf("%d", &T);
    12. while(T--)
    13. {
    14. scanf("%d", &n);
    15. _for(i, 1, n) scanf("%lld", &h[i]);
    16. _for(i, 2, n - 1) a[i] = max(max(h[i - 1], h[i + 1]) + 1 - h[i], 0ll);
    17. if(n % 2 == 1)
    18. {
    19. ll ans = 0;
    20. for(int i = 2; i <= n; i += 2) ans += a[i];
    21. printf("%lld\n", ans);
    22. continue;
    23. }
    24. vector<pair<ll, ll>> ve;
    25. for(int i = 2; i + 1 <= n; i += 2) ve.push_back({a[i], a[i + 1]});
    26. //这里两个两个打进去时,注意退出条件是i+1<=n 不是i <= n 要用这一块的终点而不是起点
    27. ll cur = 0;
    28. for(auto x: ve) cur += x.second;
    29. ll ans = cur;
    30. for(auto x: ve)
    31. {
    32. cur += x.first - x.second;
    33. ans = min(ans, cur);
    34. }
    35. printf("%lld\n", ans);
    36. }
    37. return 0;
    38. }

    Yet Another FFT Problem?(复杂度计算)

    这题的关键在于,O(n^2)的算法看起来会T,实际上不会T

    先判断a,b中有没有相等的,都有的话直接输出答案,否则去重,之后就暴力枚举a,b

    因为要输出下标,我开始的做法是将值和下标绑在一起用pair,但是这种方法写起来复杂,而且交上去MLE了,应该是vector里面直接存下标即可。

    学了学菜狗的代码,学到了很多

    1. #include <bits/stdc++.h>
    2. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    3. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    4. using namespace std;
    5. const int N = 1e7 + 10;
    6. int main()
    7. {
    8. ios::sync_with_stdio(false);
    9. cin.tie(0);
    10. int n, m;
    11. cin >> n >> m;
    12. vector<int> a(n + 1), b(m + 1);
    13. _for(i, 1, n) cin >> a[i];
    14. _for(i, 1, m) cin >> b[i];
    15. vector<int> posa(N, -1), posb(N, -1), va, vb;
    16. array<int, 4> ans = {-1, -1, -1, -1};
    17. _for(i, 1, n)
    18. {
    19. if(posa[a[i]] == -1)
    20. {
    21. posa[a[i]] = i;
    22. va.push_back(i);
    23. }
    24. else
    25. {
    26. ans[0] = posa[a[i]];
    27. ans[1] = i;
    28. }
    29. }
    30. _for(i, 1, m)
    31. {
    32. if(posb[b[i]] == -1)
    33. {
    34. posb[b[i]] = i;
    35. vb.push_back(i);
    36. }
    37. else
    38. {
    39. ans[2] = posb[b[i]];
    40. ans[3] = i;
    41. }
    42. }
    43. if(ans[0] != -1 && ans[2] != -1)
    44. {
    45. cout << ans[0] << " " << ans[1] << " " << ans[2] << " " << ans[3] << "\n";
    46. return 0;
    47. }
    48. vector<array<int, 2>> vis(2 * N, {-1, -1});
    49. for(auto i : va)
    50. for(auto j: vb)
    51. {
    52. if(vis[a[i] + b[j]][0] == -1) vis[a[i] + b[j]] = {i, j};
    53. else
    54. {
    55. cout << i << " " << vis[a[i] + b[j]][0] << " " << j << " " << vis[a[i] + b[j]][1];
    56. return 0;
    57. }
    58. }
    59. cout << "-1\n" ;
    60. return 0;
    61. }

    Good red-string(贪心)

    这道题是比较复杂的贪心,思维量比较大

    首先不能直接从左到右先填r,再填e,再填e,比如r?d???  先填r的话就会导致在前缀中d比e多

    因此我们要先处理这种特殊情况,再贪心

    首先对所有前缀需要满足cntr >= cnte >= cntd

    对于所有后缀需要满足cntd >= cnte >= cntr

    结合那个特例,可以化简成对前缀需要d >= e 对后缀需要e >= r 满足这两个条件即满足上面的式子

    因为是对称的,所以我们只用考虑前缀,后缀类似处理即可。

    对于前缀,统计e的数量和d数量,如果不满足d比e多,那就把最近一个问号填为e,显然e越靠中间越优,所以是最近的。后缀同理填e

    这样填完后再进行r e d的贪心,最后再判断是否合法。

    1. #include <bits/stdc++.h>
    2. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    3. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    4. using namespace std;
    5. bool solve()
    6. {
    7. string s; cin >> s;
    8. int len = s.size();
    9. if(len % 3) return false;
    10. vector<int> pos;
    11. int cnte = 0, cntd = 0, cntr = 0;
    12. rep(i, 0, len)
    13. {
    14. if(s[i] == 'e') cnte++;
    15. else if(s[i] == 'd') cntd++;
    16. else if(s[i] == '?') pos.push_back(i);
    17. if(cnte < cntd)
    18. {
    19. if(!pos.size()) return false;
    20. s[pos.back()] = 'e'; pos.pop_back();
    21. cnte++;
    22. }
    23. }
    24. cntr = cnte = cntd = 0;
    25. pos.clear();
    26. for(int i = len - 1; i >= 0; i--)
    27. {
    28. if(s[i] == 'e') cnte++;
    29. else if(s[i] == 'r') cntr++;
    30. else if(s[i] == '?') pos.push_back(i);
    31. else cntd++;
    32. if(cnte < cntr)
    33. {
    34. if(!pos.size()) return false;
    35. s[pos.back()] = 'e'; pos.pop_back();
    36. cnte++;
    37. }
    38. }
    39. int r = len / 3 - cntr, e = len / 3 - cnte, d = len / 3 - cntd;
    40. if(r < 0 || e < 0 || d < 0) return false;
    41. rep(i, 0, len)
    42. if(s[i] == '?')
    43. {
    44. if(r) s[i] = 'r', r--;
    45. else if(e) s[i] = 'e', e--;
    46. else if(d) s[i] = 'd', d--;
    47. }
    48. cntr = cntd = cnte = 0;
    49. rep(i, 0, len)
    50. {
    51. if(s[i] == 'r') cntr++;
    52. if(s[i] == 'e') cnte++;
    53. if(s[i] == 'd') cntd++;
    54. if(!(cntr >= cnte && cnte >= cntd)) return false;
    55. }
    56. cntr = cntd = cnte = 0;
    57. for(int i = len - 1; i >= 0; i--)
    58. {
    59. if(s[i] == 'r') cntr++;
    60. if(s[i] == 'e') cnte++;
    61. if(s[i] == 'd') cntd++;
    62. if(!(cntd >= cnte && cnte >= cntr)) return false;
    63. }
    64. return true;
    65. }
    66. int main()
    67. {
    68. int T; scanf("%d", &T);
    69. while(T--) puts(solve() ? "Yes" : "No");
    70. return 0;
    71. }

    周三

    poj 2287(贪心+分类讨论)

    比较需要思考的贪心,挺有趣的

    先看两边最高的

    首先如果左边最高的大于右边最高的,那就比一场。显然要以最小代价赢,这样赢得话两边得差是最小的,比较划算

    如果左边最高的小于右边最高的,那么这样左边怎么样都赢不了,那就拿左边最差的去送,代价最小

    如果左边最高的等于右边最高的,那就需要讨论一下

    这时有两种方法,一种是最高的比一个平局,一个是拿左边最差的去比。

    那么就需要讨论左边最差的那个有没有价值。

    如果左边最差的小于右边最差的:如果最高的比一个平局,那么就是一个平局,最差的输了,减200。如果拿左边最差的去比,输200,然而左边最好的可能大于右边第二好的。所以第二种要更优秀。

    如果左边最差的等于右边最差的,那么这时其实两种策略都是平手,都可以。

    如果左边最差的强于右边最差的。如果第一种策略,那就是平局加上赢一场,加200。如果第二种策略,那就是输一场,然后左边最高的可能赢右边第二高的。第一种策略必为+200,第二种可能+200,选第一种。

    所以综合一下上述的选择,可以发现就是最好的比一下,如果能赢就这么比,否则看最差的比一下,如果能赢就比,否则就最差的去比最好的。

    其实就是分类讨论,然后分析那种策略最优,然后总结

    1. #include <cstdio>
    2. #include <algorithm>
    3. #include <functional>
    4. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    5. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    6. using namespace std;
    7. const int N = 1e3 + 10;
    8. int a[N], b[N], n;
    9. int main()
    10. {
    11. while(scanf("%d", &n) && n)
    12. {
    13. _for(i, 1, n) scanf("%d", &a[i]);
    14. _for(i, 1, n) scanf("%d", &b[i]);
    15. sort(a + 1, a + n + 1, greater<int>());
    16. sort(b + 1, b + n + 1, greater<int>());
    17. int al = 1, ar = n, bl = 1, br = n, ans = 0;
    18. while(al <= ar)
    19. {
    20. if(a[al] > b[bl])
    21. {
    22. ans += 200;
    23. al++; bl++;
    24. }
    25. else if(a[ar] > b[br])
    26. {
    27. ans += 200;
    28. ar--; br--;
    29. }
    30. else
    31. {
    32. if(a[ar] < b[bl]) ans -= 200;
    33. ar--; bl++;
    34. }
    35. }
    36. printf("%d\n", ans);
    37. }
    38. return 0;
    39. }

    hdu 6534(莫队+树状数组+离散化)

    因为数据范围比较小,所以可以考虑莫队。

    我离散化那里卡了一下,因为不仅要把a[i]离散化,还有差为k这个限制

    比较好写的做法是将a[i]-k,a[i]+k同样扔到离散化数组里面,这样就很好处理了

    1. #include <cstdio>
    2. #include <algorithm>
    3. #include <functional>
    4. #include <cmath>
    5. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    6. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    7. using namespace std;
    8. typedef long long ll;
    9. const int N = 27000 + 10;
    10. int a[N], L[N], R[N], lsh[N * 3], cnt, n, m, k, block, t;
    11. struct query
    12. {
    13. int l, r, bl, id;
    14. }q[N];
    15. ll ans[N], f[N * 3], sum;
    16. bool cmp(query x, query y)
    17. {
    18. if(x.bl != y.bl) return x.bl < y.bl;
    19. if(x.bl & 1) return x.r < y.r;
    20. return x.r > y.r;
    21. }
    22. int lowbit(int x) { return x & -x; }
    23. void modify(int x, int p)
    24. {
    25. for(; x <= t; x += lowbit(x))
    26. f[x] += p;
    27. }
    28. ll s(int x)
    29. {
    30. ll res = 0;
    31. for(; x; x -= lowbit(x))
    32. res += f[x];
    33. return res;
    34. }
    35. void add(int x)
    36. {
    37. sum += s(R[x]) - s(L[x] - 1);
    38. modify(a[x], 1);
    39. }
    40. void erase(int x)
    41. {
    42. modify(a[x], -1);
    43. sum -= s(R[x]) - s(L[x] - 1);
    44. }
    45. int main()
    46. {
    47. scanf("%d%d%d", &n, &m, &k);
    48. _for(i, 1, n)
    49. {
    50. scanf("%d", &a[i]);
    51. lsh[++cnt] = a[i];
    52. lsh[++cnt] = a[i] - k;
    53. lsh[++cnt] = a[i] + k;
    54. }
    55. sort(lsh + 1, lsh + cnt + 1);
    56. t = unique(lsh + 1, lsh + cnt + 1) - lsh - 1;
    57. _for(i, 1, n)
    58. {
    59. L[i] = lower_bound(lsh + 1, lsh + t + 1, a[i] - k) - lsh;
    60. R[i] = lower_bound(lsh + 1, lsh + t + 1, a[i] + k) - lsh;
    61. a[i] = lower_bound(lsh + 1, lsh + t + 1, a[i]) - lsh;
    62. }
    63. block = sqrt(n);
    64. _for(i, 1, m)
    65. {
    66. int l, r; scanf("%d%d", &l, &r);
    67. q[i] = {l, r, l / block, i};
    68. }
    69. sort(q + 1, q + m + 1, cmp);
    70. int l = 1, r = 0;
    71. _for(i, 1, m)
    72. {
    73. int ll = q[i].l, rr = q[i].r;
    74. while(l < ll) erase(l++);
    75. while(l > ll) add(--l);
    76. while(r < rr) add(++r);
    77. while(r > rr) erase(r--);
    78. ans[q[i].id] = sum;
    79. }
    80. _for(i, 1, m) printf("%lld\n", ans[i]);
    81. return 0;
    82. }

    Chiitoitsu(期望dp)

    最优策略就是能配对的配对,否则仍掉

    以手上的单牌和剩下牌的总数作为状态,用期望dp

    注意边界情况

    1. #include <bits/stdc++.h>
    2. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    3. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    4. using namespace std;
    5. typedef long long ll;
    6. const int mod = 1e9 + 7;
    7. ll dp[20][150];
    8. ll binpow(ll a, ll b)
    9. {
    10. ll res = 1;
    11. for(; b; b >>= 1)
    12. {
    13. if(b & 1) res = res * a % mod;
    14. a = a * a % mod;
    15. }
    16. return res;
    17. }
    18. ll inv(ll x) { return binpow(x, mod - 2); }
    19. int main()
    20. {
    21. _for(i, 1, 13)
    22. _for(j, 3 * i, 123)
    23. dp[i][j] = (1 + 3 * i * inv(j) % mod * dp[max(i - 2, 0)][j - 1] % mod
    24. + (j - 3 * i) * inv(j) % mod * dp[i][j - 1] % mod) % mod;
    25. int T, kase = 0;
    26. scanf("%d", &T);
    27. while(T--)
    28. {
    29. string s; cin >> s;
    30. int len = s.size();
    31. map<string, int> mp;
    32. for(int i = 0; i < len; i += 2)
    33. mp[s.substr(i, 2)]++;
    34. int cnt = 0;
    35. for(auto x: mp)
    36. if(x.second == 1)
    37. cnt++;
    38. printf("Case #%d: %lld\n", ++kase, dp[cnt][123]);
    39. }
    40. return 0;
    41. }

    B. Mainak and Interesting Sequence

    比赛时当n为偶数,m为奇数的时候,构造了一下,发现构造不出来,就猜了一个肯定不成立,然后直接交了,A了。

    赛后可以证明一下,首先除了最大的数,其他的数都是成对的

    首先如果全部为一个数,那么n为偶数,和肯定为偶数,而m为奇数,排除

    那么不是全部为一个数,除了最大数外,其他的数因为是成对的,所以和为偶数,那么剩下最大数的位置也是偶数的,那么最后的和为偶数,不可能m为奇数

    1. #include <bits/stdc++.h>
    2. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    3. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    4. using namespace std;
    5. const int N = 1e5 + 10;
    6. int a[N], n, m;
    7. int main()
    8. {
    9. int T; scanf("%d", &T);
    10. while(T--)
    11. {
    12. scanf("%d%d", &n, &m);
    13. if(n % 2 == 1)
    14. {
    15. if(n > m) puts("No");
    16. else
    17. {
    18. _for(i, 1, n - 1) a[i] = 1, m--;
    19. a[n] = m;
    20. puts("Yes");
    21. _for(i, 1, n) printf("%d ", a[i]); puts("");
    22. }
    23. }
    24. else
    25. {
    26. if(m % 2 == 1) puts("No");
    27. else
    28. {
    29. if(n > m) puts("No");
    30. else
    31. {
    32. _for(i, 1, n - 2) a[i] = 1, m--;
    33. a[n - 1] = a[n] = m / 2;
    34. puts("Yes");
    35. _for(i, 1, n) printf("%d ", a[i]); puts("");
    36. }
    37. }
    38. }
    39. }
    40. return 0;
    41. }

    C. Jatayu's Balanced Bracket Sequence

    有两种边,一种是括号匹配的,一种是匹配与匹配之间的。比赛时用并查集写的,实际上并不会成环,可以直接统计右括号和)(的个数。

    1. #include <bits/stdc++.h>
    2. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    3. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    4. using namespace std;
    5. const int N = 2e5 + 10;
    6. int f[N], st[N], top, n, cnt;
    7. int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); }
    8. void merge(int x, int y)
    9. {
    10. int fx = find(x), fy = find(y);
    11. if(fx != fy)
    12. {
    13. cnt--;
    14. f[fx] = fy;
    15. }
    16. }
    17. int main()
    18. {
    19. int T; scanf("%d", &T);
    20. while(T--)
    21. {
    22. scanf("%d", &n);
    23. _for(i, 1, 2 * n) f[i] = i;
    24. top = 0;
    25. cnt = 2 * n;
    26. string s; cin >> s;
    27. s = " " + s;
    28. int pre;
    29. _for(i, 1, 2 * n)
    30. {
    31. if(s[i] == '(')
    32. {
    33. if(s[i - 1] == ')') merge(i, pre);
    34. st[++top] = i;
    35. }
    36. else
    37. {
    38. pre = i;
    39. merge(i, st[top--]);
    40. }
    41. }
    42. printf("%d\n", cnt);
    43. }
    44. return 0;
    45. }

    D. Edge Split

    这题等相通了发现不难,但比赛时还是想了一个小时,很不应该。

    显然可以生成树一种颜色,非树边一种颜色。当非数边成环时,将一条边加到生成树中,生成树新成的环删去一条边即可

    1. #include <bits/stdc++.h>
    2. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    3. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    4. using namespace std;
    5. const int N = 2e5 + 10;
    6. vector<pair<int, int>> g[N];
    7. int ans[N], vis[N], d[N], f[N], n, m;
    8. vector<array<int, 3>> edge;
    9. void dfs(int u, int fa)
    10. {
    11. d[u] = d[fa] + 1;
    12. vis[u] = 1;
    13. for(auto x: g[u])
    14. {
    15. int v = x.first, id = x.second;
    16. if(v == fa)
    17. {
    18. f[u] = id;
    19. continue;
    20. }
    21. if(vis[v])
    22. {
    23. int flag = 1;
    24. for(auto x: edge)
    25. if(x[2] == id)
    26. {
    27. flag = 0;
    28. break;
    29. }
    30. if(flag) edge.push_back({u, v, id});
    31. }
    32. else dfs(v, u);
    33. }
    34. }
    35. int main()
    36. {
    37. int T; scanf("%d", &T);
    38. while(T--)
    39. {
    40. scanf("%d%d", &n, &m);
    41. edge.clear();
    42. _for(i, 1, n) g[i].clear(), vis[i] = 0;
    43. _for(i, 1, m) ans[i] = 0;
    44. _for(i, 1, m)
    45. {
    46. int u, v;
    47. scanf("%d%d", &u, &v);
    48. g[u].push_back({v, i});
    49. g[v].push_back({u, i});
    50. }
    51. dfs(1, 0);
    52. int flag = 0;
    53. if(edge.size() == 3)
    54. {
    55. set<int> s;
    56. for(auto x: edge)
    57. {
    58. s.insert(x[0]);
    59. s.insert(x[1]);
    60. }
    61. if(s.size() == 3) flag = 1;
    62. }
    63. if(!flag) for(auto x: edge) ans[x[2]] = 1;
    64. else
    65. {
    66. ans[edge[0][2]] = ans[edge[1][2]] = 1;
    67. int u = edge[2][0], v = edge[2][1];
    68. if(d[u] < d[v]) swap(u, v);
    69. ans[f[u]] = 1;
    70. }
    71. _for(i, 1, m) printf("%d", ans[i]); puts("");
    72. }
    73. return 0;
    74. }

    A2. Burenka and Traditions (hard version)(异或+贪心区间覆盖)

    这题是由三个关键点嵌套起来。

    1.花费为区间除以2向上取整

    任何一个操作的花费都可以拆分成长度为1和长度为2的操作,这样考虑一定包含的最优的答案。这样拆分可以简化问题

    这样子答案的上界就是n,每个数都区间长度为1的操作

    为了让花费最小,显然要尽可能多的操作区间为2的且一起为0,也就是相等。

    考虑对于ai-1 和ai  如果本来就不等,那么如果使它们相等呢。在ai-1之前可以一直操作区间为2的,比如说a6,它可以通过前面的操作变成a6^a5,a6^a5^a4,a6^a5^a4^a3……

    那么怎么判断是否可以使得相等呢,这就用到了异或的性质,是第二个关键点

    2.异或的性质

    直接判断比较难,我们加入前缀异或和,也就是从1到ai-1,记为sum

    那么也就是存在前缀异或和为sum ^ a[i],注意这个前缀异或和包括0

    也就是对于一个ai,可以判断可能存在一个区间,使得答案更小。

    那么就由很多个区间,就转化为区间覆盖问题

    3.贪心求区间覆盖

    结论是按照右端点排序,然后遍历,能放就放

    因为可以按照右端点排序,所以可以直接枚举a1到an,然后如果当前可以使答案更小,那就操作,操作之后前缀异或和要重新记录,注意0始终存在。

    1. #include <bits/stdc++.h>
    2. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    3. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    4. using namespace std;
    5. const int N = 1e5 + 10;
    6. int a[N], n;
    7. int main()
    8. {
    9. int T; scanf("%d", &T);
    10. while(T--)
    11. {
    12. scanf("%d", &n);
    13. _for(i, 1, n) scanf("%d", &a[i]);
    14. set<int> s;
    15. s.insert(0);
    16. int ans = n, sum = 0;
    17. _for(i, 1, n)
    18. {
    19. if(s.count(sum ^ a[i]))
    20. {
    21. ans--;
    22. s.clear(); s.insert(0);
    23. sum = 0;
    24. }
    25. else sum ^= a[i], s.insert(sum);
    26. }
    27. printf("%d\n", ans);
    28. }
    29. return 0;
    30. }

    周四

    D. Madoka and The Corruption Scheme(思维+组合数)

    这题的关键在于反过来看,我一直按照题目的意思来看,结果感觉比较复杂。

    逆向思考,其实就是从根节点,每次选一条红边往下到叶子,叶子就是胜者。

    那么显然,要改变的话,只有改变这条路径上的边才有用,而且不同改变的结果一定是不同的,可以看作线段树向下,每次向左边或者向右边,只要有一次不同,结果一定不同。

    考虑改变能产生的不同的结果,修改0次就是c(n, 0) 修改1次就是c(n,1)……

    令k=min(k, n) 那么结果一共有c(n, 0) + c(n,1)……c(n, k)这么多可能

    那改变的人肯定取这么多可能的最大值,那么布置的人一定是布置1,2,3……这样可以使得最大值最小。

    所以最终的答案就是这个和

    预处理阶乘即可

    1. #include <bits/stdc++.h>
    2. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    3. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    4. using namespace std;
    5. typedef long long ll;
    6. const int N = 1e5 + 10;
    7. const int mod = 1e9 + 7;
    8. ll f[N];
    9. ll binpow(ll a, ll b)
    10. {
    11. ll res = 1;
    12. for(; b; b >>= 1)
    13. {
    14. if(b & 1) res = res * a % mod;
    15. a = a * a % mod;
    16. }
    17. return res;
    18. }
    19. ll inv(ll x) { return binpow(x, mod - 2); }
    20. ll C(ll n, ll m)
    21. {
    22. return f[n] * inv(f[m]) % mod * inv(f[n - m]) % mod;
    23. }
    24. int main()
    25. {
    26. f[0] = 1;
    27. _for(i, 1, 1e5) f[i] = f[i - 1] * i % mod;
    28. int n, k;
    29. scanf("%d%d", &n, &k);
    30. k = min(k, n);
    31. ll ans = 0;
    32. _for(i, 0, k) ans = (ans + C(n, i)) % mod;
    33. printf("%lld\n", ans);
    34. return 0;
    35. }

    Build a Tree and That Is It(思维+解方程)

    可以发现只有两种情况,要不是链式,要不是4号为根,然后1 2 3连向4

    第一种情况很好处理,第二种情况需要解方程。如果解出小数或者小于等于0就无解。

    最后还要注意一下点数和边数要满足限制。

    写的时候有想到,但是以为不会超,但实际上是会超的,WA了一发。其实写了也没错,应该一开始就写上去。

    1. #include <bits/stdc++.h>
    2. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    3. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    4. using namespace std;
    5. int n, d12, d23, d13, cnt, root;
    6. vector<pair<int, int>> ans;
    7. int dis[5][5];
    8. void add(int u, int x)
    9. {
    10. vector<int> ve;
    11. ve.push_back(u);
    12. _for(i, 1, x - 1) ve.push_back(cnt++);
    13. ve.push_back(root);
    14. rep(i, 0, ve.size() - 1) ans.push_back({ve[i], ve[i + 1]});
    15. }
    16. int main()
    17. {
    18. int T; scanf("%d", &T);
    19. while(T--)
    20. {
    21. cnt = 4;
    22. ans.clear();
    23. scanf("%d%d%d%d", &n, &d12, &d23, &d13);
    24. dis[1][2] = dis[2][1] = d12;
    25. dis[2][3] = dis[3][2] = d23;
    26. dis[1][3] = dis[3][1] = d13;
    27. //case 1
    28. vector<pair<int, int>> ve;
    29. ve.push_back({d12, 3});
    30. ve.push_back({d23, 1});
    31. ve.push_back({d13, 2});
    32. sort(ve.begin(), ve.end());
    33. if(ve[0].first + ve[1].first == ve[2].first)
    34. {
    35. root = ve[2].second;
    36. _for(i, 1, 3)
    37. if(i != root)
    38. add(i, dis[i][root]);
    39. }
    40. else //case 2
    41. {
    42. if((d13 + d12 - d23) % 2 != 0)
    43. {
    44. puts("NO");
    45. continue;
    46. }
    47. int a, b, c;
    48. a = (d13 + d12 - d23) / 2;
    49. c = d13 - a;
    50. b = d23 - c;
    51. if(a <= 0 || b <= 0 || c <= 0)
    52. {
    53. puts("NO");
    54. continue;
    55. }
    56. root = cnt++;
    57. if(root > n)
    58. {
    59. puts("NO");
    60. continue;
    61. }
    62. add(1, a); add(2, b); add(3, c);
    63. }
    64. while(cnt <= n) ans.push_back({cnt++, root});
    65. if(cnt > n + 1) puts("NO");
    66. else
    67. {
    68. puts("YES");
    69. for(auto [u, v]: ans) printf("%d %d\n", u, v);
    70. }
    71. }
    72. return 0;
    73. }

    D. Magical Array(思维)

    妙啊,我自己思考的时候就觉得操作1和操作2会使得序列的某个性质改变,通过这个性质判断

    一开始想的是和,但是都是使得和不变的

    突破口是操作2的下标不同,所以我们把下标加入到序列的性质中,也就是ai * i的求和,可以发现操作2使得这个值+1,操作一不变,通过这个即可得出答案

    1. #include <bits/stdc++.h>
    2. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    3. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    4. using namespace std;
    5. typedef long long ll;
    6. const int N = 1e5 + 10;
    7. vector<pair<ll, int>> ve;
    8. int main()
    9. {
    10. int T; scanf("%d", &T);
    11. while(T--)
    12. {
    13. ve.clear();
    14. int n, m;
    15. scanf("%d%d", &n, &m);
    16. _for(i, 1, n)
    17. {
    18. ll sum = 0;
    19. _for(j, 1, m)
    20. {
    21. ll x; scanf("%lld", &x);
    22. sum += x * j;
    23. }
    24. ve.push_back({sum, i});
    25. }
    26. sort(ve.begin(), ve.end());
    27. printf("%d %lld\n", ve.back().second, ve.back().first - ve[0].first);
    28. }
    29. return 0;
    30. }

    G1. Passable Paths(dfs)

    我的思路是找所有点的lca,然后从这开始搜,看经过这一点的路径最大值是否为k。

    题目的思路简单一些,找深度最大的节点dfs,这个节点一定是路径的一端,然后后dfs能否走过所有选择的点

    1. #include <bits/stdc++.h>
    2. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    3. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    4. using namespace std;
    5. const int N = 2e5 + 10;
    6. vector<int> g[N];
    7. int d[N], up[N][21], c[N], n, q, k, ans, root;
    8. void dfs(int u, int fa)
    9. {
    10. d[u] = d[fa] + 1;
    11. up[u][0] = fa;
    12. _for(j, 1, 20) up[u][j] = up[up[u][j - 1]][j - 1];
    13. for(int v: g[u])
    14. {
    15. if(v == fa) continue;
    16. dfs(v, u);
    17. }
    18. }
    19. int lca(int u, int v)
    20. {
    21. if(d[u] < d[v]) swap(u, v);
    22. for(int j = 20; j >= 0; j--)
    23. if(d[up[u][j]] >= d[v])
    24. u = up[u][j];
    25. if(u == v) return u;
    26. for(int j = 20; j >= 0; j--)
    27. if(up[u][j] != up[v][j])
    28. u = up[u][j], v = up[v][j];
    29. return up[u][0];
    30. }
    31. int dfs2(int u, int fa)
    32. {
    33. int mx1 = 0, mx2 = 0;
    34. for(int v: g[u])
    35. {
    36. if(v == fa) continue;
    37. int t = dfs2(v, u);
    38. if(t > mx1) mx2 = mx1, mx1 = t;
    39. else mx2 = max(mx2, t);
    40. }
    41. if(u == root && c[u] + mx1 + mx2 == k) ans = 1;
    42. return c[u] + mx1;
    43. }
    44. int main()
    45. {
    46. scanf("%d", &n);
    47. _for(i, 1, n) g[i].clear();
    48. _for(i, 1, n - 1)
    49. {
    50. int u, v;
    51. scanf("%d%d", &u, &v);
    52. g[u].push_back(v);
    53. g[v].push_back(u);
    54. }
    55. dfs(1, 0);
    56. scanf("%d", &q);
    57. _for(i, 1, q)
    58. {
    59. root = 0;
    60. _for(i, 1, n) c[i] = 0;
    61. scanf("%d", &k);
    62. _for(j, 1, k)
    63. {
    64. int x; scanf("%d", &x);
    65. c[x] = 1;
    66. if(!root) root = x;
    67. else root = lca(root, x);
    68. }
    69. ans = 0;
    70. dfs2(root, 0);
    71. puts(ans ? "YES" : "NO");
    72. }
    73. return 0;
    74. }

    D. Permutation Restoration(贪心)

    首先可以算出一个可能的取值范围,注意当分数小于一个值,转化为开区间可以向下取整+1

    然后就变成了怎么给每个区间分配点,我在这里卡住了,想把区间排序,然后不太行。

    正解是反过来思考,给点分配区间,从1遍历到n,对于当前点,给它分配一个右端点最小的区间。

    1. #include <bits/stdc++.h>
    2. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    3. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    4. using namespace std;
    5. const int N = 5e5 + 10;
    6. vector<int> v[N];
    7. int b[N], a[N], n;
    8. int main()
    9. {
    10. int T; scanf("%d", &T);
    11. while(T--)
    12. {
    13. scanf("%d", &n);
    14. _for(i, 1, n) v[i].clear();
    15. _for(i, 1, n)
    16. {
    17. scanf("%d", &b[i]);
    18. int l = i / (b[i] + 1) + 1;
    19. v[l].push_back(i);
    20. }
    21. set<pair<int, int>> s;
    22. _for(i, 1, n)
    23. {
    24. for(int id: v[i]) s.insert({(b[id] == 0) ? n : id / b[id], id});
    25. a[s.begin()->second] = i;
    26. s.erase(s.begin());
    27. }
    28. _for(i, 1, n) printf("%d ", a[i]); puts("");
    29. }
    30. return 0;
    31. }

    周五

    D. Guess The String(交互题+二分)

    交互题做的非常少……

    这题提供的主要信息就是一段区间右多少种字符

    考虑一个一个字符来猜,如果前面字符已知,现在猜第i个字符

    考虑f(j, i) 和f(j, i-1)  f(x,y)表示[x, y]中有多少种字符

    f(j, i) 和f(j, i-1)的差别就是多了一个s[i]

    显然,如果s[i]在(j,i-1)未出现过,那么f(j, i)会大1,否则相等

    当j离i越近,(j,i-1)就越小,越难包含s[i],如果j离i越远,那么就越可能包含s[i]

    考虑f(j, i) - f(j, i - 1) 那么随着j的增大,它的值一定是0 0 0 0 0 1 1 1 1

    最右边的0的位置就是s[i]

    因为0011具有单调性,所以我们可以二分来找这个s[i]

    当然,右可能前面每出现过找不到,此时就询问i

    那么这种做法询问是比较多的,因为有log(1000)    6000的数据范围意味着每个i最多6次

    考虑优化,显然只有每个字母最后出现的位置是有意义的,那么我们可以只存这个,那么二分次数就大大减小。

    1. #include <bits/stdc++.h>
    2. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    3. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    4. using namespace std;
    5. const int N = 1e3 + 10;
    6. int pos[30], cnt, n;
    7. char ans[N];
    8. char ask1(int x)
    9. {
    10. printf("? 1 %d\n", x);
    11. fflush(stdout);
    12. string s; cin >> s; //输入一个字符时要额外小心 应该这么写
    13. return s[0];
    14. }
    15. int ask2(int l, int r)
    16. {
    17. printf("? 2 %d %d\n", l, r);
    18. fflush(stdout);
    19. int t; scanf("%d", &t);
    20. return t;
    21. }
    22. int main()
    23. {
    24. scanf("%d", &n);
    25. ans[1] = ask1(1);
    26. pos[++cnt] = 1;
    27. _for(i, 1, n)
    28. {
    29. sort(pos + 1, pos + cnt + 1);
    30. int l = 0, r = cnt + 1;
    31. while(l + 1 < r)
    32. {
    33. int m = l + r >> 1;
    34. if(ask2(pos[m], i) == (cnt - m + 1)) l = m;
    35. else r = m;
    36. }
    37. if(l == 0)
    38. {
    39. ans[i] = ask1(i);
    40. pos[++cnt] = i;
    41. }
    42. else
    43. {
    44. ans[i] = ans[pos[l]];
    45. pos[l] = i;
    46. }
    47. }
    48. printf("! ");
    49. _for(i, 1, n) putchar(ans[i]);
    50. fflush(stdout);
    51. return 0;
    52. }

    D. Permutation Graph(单调栈+线段树)

    这题妙啊,有两种做法。

    第一种做法比较直接,但写起来复杂一些,第二种不太好想到,但是写起来简单

    第一种做法

    首先,有一个贪心猜测,每一次走尽可能的远。我一开始有这个想法,但不知道这个贪心是不是对的。其实很好证明,如果u最远能达到v,那么u和v中间的节点一定不能连一条边超过v。因为如果可以,比如中间有个节点a,连一条边到v右边的b,那么可以发现u是可以连边到b的,就不满足v是最远的这个前提。

    有这个结论后,考虑怎么迅速求这个最远的点,比如当前节点是i,那么a[i]与a[i+1]的大小关系显然决定了a[i]是最大值还是最小值,如果a[i] > a[i+1]那么显然a[i]要作为最大值

    如果a[i]是作为最大值的话,设j为离i最近的使得a[j] > a[i],那么可能得答案在[i + 1, j - 1]中

    这个j可以用单调栈O(n)求出

    在可能的答案区间中,求最小值,这个最小值就是我们要的答案。因为不可能有边从i越过这个最小值,因为i已经是最小值了。所以可以用线段树预处理区间最值,同时存对应的下标(用pair写起来非常简洁),那么就可以迅速求出

    每次跳一次的复杂度是线段树的O(logn),而每次至少可以往右移动一个,那么最多跳n次,所以复杂度是O(nlogn)的

    1. #include <bits/stdc++.h>
    2. #define l(k) (k << 1)
    3. #define r(k) (k << 1 | 1)
    4. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    5. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    6. using namespace std;
    7. typedef pair<int, int> pa;
    8. const int N = 2.5e5 + 10;
    9. int a[N], rmax[N], rmin[N], st[N], top, n;
    10. pa tmax[N << 2], tmin[N << 2];
    11. void up(int k)
    12. {
    13. tmax[k] = max(tmax[l(k)], tmax[r(k)]);
    14. tmin[k] = min(tmin[l(k)], tmin[r(k)]);
    15. }
    16. void build(int k, int l, int r)
    17. {
    18. if(l == r)
    19. {
    20. tmax[k] = tmin[k] = {a[l], l};
    21. return;
    22. }
    23. int m = l + r >> 1;
    24. build(l(k), l, m);
    25. build(r(k), m + 1, r);
    26. up(k);
    27. }
    28. pa ask_max(int k, int l, int r, int L, int R)
    29. {
    30. if(L <= l && r <= R) return tmax[k];
    31. int m = l + r >> 1;
    32. pa res = {0, 0};
    33. if(L <= m) res = max(res, ask_max(l(k), l, m, L, R));
    34. if(R > m) res = max(res, ask_max(r(k), m + 1, r, L, R));
    35. return res;
    36. }
    37. pa ask_min(int k, int l, int r, int L, int R)
    38. {
    39. if(L <= l && r <= R) return tmin[k];
    40. int m = l + r >> 1;
    41. pa res = {1e9, 0};
    42. if(L <= m) res = min(res, ask_min(l(k), l, m, L, R));
    43. if(R > m) res = min(res, ask_min(r(k), m + 1, r, L, R));
    44. return res;
    45. }
    46. int main()
    47. {
    48. int T; scanf("%d", &T);
    49. while(T--)
    50. {
    51. scanf("%d", &n);
    52. _for(i, 1, n) scanf("%d", &a[i]);
    53. top = 0;
    54. for(int i = n; i >= 1; i--) //单调栈不用先加入 直接从n开始
    55. {
    56. while(top && a[i] > a[st[top]]) top--;
    57. rmax[i] = top ? st[top] : n + 1; //注意栈未空的情况
    58. st[++top] = i;
    59. }
    60. top = 0;
    61. for(int i = n; i >= 1; i--)
    62. {
    63. while(top && a[i] < a[st[top]]) top--;
    64. rmin[i] = top ? st[top] : n + 1;
    65. st[++top] = i;
    66. }
    67. build(1, 1, n);
    68. int ans = 0, cur = 1;
    69. while(cur != n)
    70. {
    71. if(a[cur + 1] > a[cur])
    72. {
    73. pa t = ask_max(1, 1, n, cur + 1, rmin[cur] - 1);
    74. cur = t.second;
    75. }
    76. else
    77. {
    78. pa t = ask_min(1, 1, n, cur + 1, rmax[cur] - 1);
    79. cur = t.second;
    80. }
    81. ans++;
    82. }
    83. printf("%d\n", ans);
    84. }
    85. return 0;
    86. }

    第二种做法

    核心是一个点:不会有边跨过最值

    对于ai=n,一定不会有边跨过它,对于aj=1也是

    不能跨国它意味着一定要经过它 

    同时i和j存在一条边

    假设i

    所以dis(1, n) = dis(1, i) + 1 + dis(j, n)

    那么可以这样一直递归下去,只不过最值变成了当前区间的最值

    这里的区间要不是l=1,要不是r=n,所以只需要处理前缀最值和后缀最值

    还要存值对应的下标

    1. #include <bits/stdc++.h>
    2. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    3. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    4. using namespace std;
    5. typedef pair<int, int> pa;
    6. const int N = 2.5e5 + 10;
    7. int a[N], lmax[N], lmin[N], rmax[N], rmin[N], id[N], n;
    8. int dfs(int l, int r)
    9. {
    10. if(l == r) return 0;
    11. int x, y;
    12. if(l == 1)
    13. {
    14. x = min(id[lmax[r]], id[lmin[r]]);
    15. y = max(id[lmax[r]], id[lmin[r]]);
    16. }
    17. else
    18. {
    19. x = min(id[rmax[l]], id[rmin[l]]);
    20. y = max(id[rmax[l]], id[rmin[l]]);
    21. }
    22. return dfs(l, x) + 1 + dfs(y, r);
    23. }
    24. int main()
    25. {
    26. int T; scanf("%d", &T);
    27. while(T--)
    28. {
    29. scanf("%d", &n);
    30. _for(i, 1, n) scanf("%d", &a[i]), id[a[i]] = i;
    31. lmax[1] = lmin[1] = a[1];
    32. _for(i, 2, n)
    33. {
    34. lmax[i] = max(lmax[i - 1], a[i]);
    35. lmin[i] = min(lmin[i - 1], a[i]);
    36. }
    37. rmax[n] = rmin[n] = a[n];
    38. for(int i = n - 1; i >= 1; i--)
    39. {
    40. rmax[i] = max(rmax[i + 1], a[i]);
    41. rmin[i] = min(rmin[i + 1], a[i]);
    42. }
    43. printf("%d\n", dfs(1, n));
    44. }
    45. return 0;
    46. }

    D. Lena and Matrix(拆式子)

    这题的关键在于把题目给的式子变换,拆开

    分类一下把那个绝对值拆开,发现最值一定是4个点之一,所以有用的只有4个点

    1. #include <bits/stdc++.h>
    2. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    3. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    4. using namespace std;
    5. const int N = 1e3 + 10;
    6. char s[N][N];
    7. int n, m;
    8. int main()
    9. {
    10. int T; scanf("%d", &T);
    11. while(T--)
    12. {
    13. vector<pair<int, int>> node(4, {-1, -1});
    14. scanf("%d%d", &n, &m);
    15. _for(i, 1, n)
    16. {
    17. scanf("%s", s[i] + 1);
    18. _for(j, 1, m)
    19. {
    20. if(s[i][j] == 'W') continue;
    21. if(node[0].first == -1)
    22. {
    23. rep(k, 0, 4)
    24. node[k] = {i, j};
    25. }
    26. else
    27. {
    28. if(i + j > node[0].first + node[0].second) node[0] = {i, j};
    29. if(i + j < node[1].first + node[1].second) node[1] = {i, j};
    30. if(i - j > node[2].first - node[2].second) node[2] = {i, j};
    31. if(i - j < node[3].first - node[3].second) node[3] = {i, j};
    32. }
    33. }
    34. }
    35. int ans = 1e9, x, y;
    36. _for(i, 1, n)
    37. _for(j, 1, m)
    38. {
    39. int cur = 0;
    40. for(auto [xx, yy]: node)
    41. cur = max(cur, abs(xx - i) + abs(yy - j));
    42. if(cur < ans)
    43. {
    44. ans = cur;
    45. x = i; y = j;
    46. }
    47. }
    48. printf("%d %d\n", x, y);
    49. }
    50. return 0;
    51. }

  • 相关阅读:
    【特征选择】基于二进制粒子群算法的特征选择方法(GRNN广义回归神经网络分类)【Matlab代码#32】
    Allegro Design Entry HDL(OrCAD Capture HDL)软件设计管理图标详细介绍
    SSM SpringBoot vue学校办公自动化系统
    获取嵌套结构体字段总数
    docker搭建Jenkins及基本使用
    vue课后习题及答案
    SpringCloud微服务-SpringAMQP(RabbitMQ)
    LayaBox---TypeScript---类型兼容性
    微服务(基础篇-007-RabbitMQ部署指南)
    现代面试中的乱象
  • 原文地址:https://blog.csdn.net/qq_34416123/article/details/126700290