[关闭]
@2368860385 2020-11-07T03:03:05.000000Z 字数 2619 阅读 180

2018.9.21周五

比赛总结


T1

在树上判两条路径是否有相交。
判一对的LCA是否在另一对的路径上。

T2

单调栈 或者 直接bit求左右最小最大值。
首先:统计答案时用所有的最大值的和减去所有的最小值的和。
考虑扫到一个右端点的时候,前面所有作为左端点形成的区间的答案。
x是1~5的最小值,那么1,2,3,4,5~r的最小值就都是x了。所以可以维护一个单调递减的栈,并记录每个值所有覆盖的数,比如x可以覆盖5个数。
o o o o x o o o r
同样的,在记录一个单调栈,递增的,维护最大值。再记录两个变量维护和即可。

T3

启发式合并。

  1. /*
  2. n^2:
  3. 去掉一条边,计算这条边的出现的次数
  4. 比较难计算,正难则反,计算所有的数对-未出现的次数。
  5. 所以转化为求一天边未出现在那些数对中:
  6. 考虑去掉这条边,然后就成了两个联通块,然后如果l,r中的所有点都在一边,那么就可以去掉。
  7. 于是可以求出这些连续的在一边的数字 l,l+1,l+2...r,那么就有(r-l+1)个数这些可以两两组合,可以O(1)算。
  8. nlogn:
  9. 考虑优化。
  10. 对于考虑用子树的答案取更新出当前点的答案。
  11. 启发式合并。用siz小的子树合并到siz大的,得到整棵树的答案。
  12. 具体见注释。
  13. */
  14. #include<cstdio>
  15. #include<algorithm>
  16. #include<cstring>
  17. #include<cmath>
  18. #include<iostream>
  19. #include<cctype>
  20. #include<set>
  21. #include<vector>
  22. #include<queue>
  23. #include<map>
  24. #define fi(s) freopen(s,"r",stdin);
  25. #define fo(s) freopen(s,"w",stdout);
  26. using namespace std;
  27. typedef long long LL;
  28. inline int read() {
  29. int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
  30. for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f;
  31. }
  32. const int N = 100005;
  33. int siz[N], fa[N], son[N], f[N], s[N], n;
  34. int head[N], nxt[N << 1], to[N << 1], En;
  35. LL Sum, Sum2, Answer;
  36. bool vis[N];
  37. set<int>st;
  38. set<int> :: iterator it, it1, it2;
  39. void add_edge(int u,int v) {
  40. ++En; to[En] = v; nxt[En] = head[u]; head[u] = En;
  41. ++En; to[En] = u; nxt[En] = head[v]; head[v] = En;
  42. }
  43. void dfs1(int u) {
  44. siz[u] = 1;
  45. for (int i=head[u]; i; i=nxt[i]) {
  46. int v = to[i];
  47. if (v == fa[u]) continue;
  48. fa[v] = u;
  49. dfs1(v);
  50. siz[u] += siz[v];
  51. if (!son[u] || siz[v] > siz[son[u]]) son[u] = v;
  52. }
  53. }
  54. int find(int x) {
  55. return x == f[x] ? x : f[x] = find(f[x]);
  56. }
  57. inline LL Calc(int x) {
  58. return (1ll * (x) * (x - 1)) / 2;
  59. }
  60. void add(int x) { // 子树里加一个点(合并的时候,新加一个点)
  61. st.insert(x); // 求子树外的答案,加子树内的点。一个点到子树内,就是从子树外拿走了,所以子树外的连续区间就可能断了
  62. it1 = it2 = it = st.find(x); it1--, it2 ++;
  63. Sum2 -= Calc((*it2) - (*it1) - 1); // 以前的连续区间的长度
  64. Sum2 += Calc((*it) - (*it1) - 1) + Calc((*it2) - (*it) - 1); // 这个拿走后
  65. vis[x] = 1;
  66. if (vis[x - 1]) { // 和以前的点合并构成新的区间。
  67. int u = find(x - 1), v = find(x);
  68. Sum += 1ll * s[u] * s[v];
  69. f[u] = v; s[v] += s[u];
  70. }
  71. if (vis[x + 1]) {
  72. int u = find(x + 1), v = find(x);
  73. Sum += 1ll * s[u] * s[v];
  74. f[u] = v; s[v] += s[u];
  75. }
  76. }
  77. void bfs(int u) {
  78. add(u);
  79. for (int i=head[u]; i; i=nxt[i])
  80. if (to[i] != fa[u]) bfs(to[i]);
  81. }
  82. void Clear(int u) {
  83. s[u] = 1, f[u] = u, vis[u] = 0;
  84. for (int i=head[u]; i; i=nxt[i])
  85. if (to[i] != fa[u]) Clear(to[i]);
  86. }
  87. void dfs2(int u) {
  88. for (int i=head[u]; i; i=nxt[i]) {
  89. int v = to[i];
  90. if (v == fa[u] || v == son[u]) continue;
  91. dfs2(v); // 计算u->v这条边的答案
  92. Clear(v); // 清空,防止计算下一个点的时候,产生影响
  93. st.clear(), st.insert(0), st.insert(n + 1);
  94. Sum = 0, Sum2 = Calc(n);
  95. }
  96. if (son[u]) dfs2(son[u]); // 最后一次搜索,不清空,st,vis含有son[u]的子树的信息。
  97. for (int i=head[u]; i; i=nxt[i])
  98. if (to[i] != fa[u] && to[i] != son[u]) bfs(to[i]);
  99. add(u);
  100. Answer += Calc(n) - Sum - Sum2;
  101. }
  102. int main() {
  103. freopen("treecnt.in","r",stdin);
  104. freopen("treecnt.out","w",stdout);
  105. n = read();
  106. st.clear(), st.insert(0), st.insert(n + 1);
  107. Sum2 = Calc(n);
  108. for (int i=1; i<n; ++i) {
  109. int u = read(), v = read();
  110. add_edge(u, v);
  111. }
  112. dfs1(1);
  113. for (int i=1; i<=n; ++i) f[i] = i, s[i] = 1;
  114. dfs2(1);
  115. cout << Answer;
  116. return 0;
  117. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注