[关闭]
@wsndy-xx 2019-08-14T07:51:56.000000Z 字数 4416 阅读 709

Noip 2019 冲刺


多做几套模拟题,体会心路历程。


time 8.13

这套题的三道题不是同一时间做的,毕竟也没有机会创造 3.5 h。

T1 选择区间

输入 m 个区间 [x, y],你希望从中选择尽量多的区间,使得两两不相交,也不能有公共端点。
其中 m <= 100000, x,y <= 1e9

算法分析
from mjt
对区间端点离散化后直接 n*logn 的 dp

from lyq
考虑不离散化的做法,直接对区间进行二分是错误的,错误也非常明显,无论怎么排序,然而对于一个区间 q,如果 q 完全包含于区间 p,显然 p 是无用的。因此首先将无用区间剔除,就可以非常简单的二分得到想要的。

得分分析
我可以在20分钟内实现一个70分的做法,同样有信心在 2h 内解决这道题
如果放在一年前同样不知道是否可以用更短的时间解决这道题,因为这道题涉及自己最不擅长的 dp

代码

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 101000;
  4. struct Node {
  5. int l, r;
  6. bool notuse;
  7. } A[N];
  8. int f[N];
  9. int maxn[N];
  10. Node B[N];
  11. int js;
  12. bool Cmp(Node a, Node b) {
  13. if(a.l == b.l) return a.r < b.r;
  14. return a.l < b.l;
  15. }
  16. int n;
  17. int Find(int R, int x) {
  18. int l = 1, r = R, Ans = 1;
  19. while(l <= r) {
  20. int mid = (l + r) >> 1;
  21. if(A[mid].r < x) Ans = mid, l = mid + 1;
  22. else r = mid - 1;
  23. }
  24. return Ans;
  25. }
  26. int main() {
  27. cin >> n;
  28. for(int i = 1; i <= n; i ++) cin >> A[i].l >> A[i].r;
  29. sort(A + 1, A + n + 1, Cmp);
  30. int l = 1;
  31. for(int i = 1; i <= n; i ++)
  32. for(int j = i + 1; j <= n; j ++)
  33. if(A[j].l > A[i].l && A[j].r < A[i].r) {
  34. A[i].notuse = 1;
  35. break;
  36. }
  37. for(int i = 1; i <= n; i ++) if(A[i].notuse == 0) B[++ js] = A[i];
  38. n = js;
  39. for(int i = 1; i <= n; i ++) A[i] = B[i];
  40. for(int i = 1; i <= n; i ++) {
  41. if(i == 1) {
  42. maxn[i] = 1;
  43. f[1] = 1; continue;
  44. }
  45. int wei = Find(i - 1, A[i].l);
  46. if(A[wei].r < A[i].l) {
  47. f[i] = maxn[wei] + 1;
  48. maxn[i] = max(maxn[i - 1], f[i]);
  49. } else {
  50. f[i] = 1;
  51. maxn[i] = max(maxn[i - 1], 1);
  52. }
  53. }
  54. int ans = 0;
  55. for(int i = 1; i <= n; i ++) ans = max(ans, f[i]);
  56. cout << ans;
  57. return 0;
  58. }

T2 纲举目张

输入一棵n个点的树,每条边的长度是1,
一共q次询问,每次询问k个点,
B君希望在树上保留最少的边,使得询问给出的k个点是连通的。
输出最小的边数
其中 1<=n<=1e5,1<=q<=1e5,2<=k<=4

算法分析
对于 k=2 的数据直接输出树上两点间的距离。
对于全体数据
对任意两点之间从树上抽离出区间路径,然后类似线段求交统计答案
显然区间路径的建立是依靠树链剖分来实现的。
时间复杂度 q * logn * logn * C(不大的常数)??
特别注意的是边权向点权的转化,一直没想到。

得分分析
可以在 20 分钟内拿下 30 分
可以在 2h 内解决这道题
不过要是放在一年前,感觉自己会非常快的想到这道题自己的正解,并且在 1.5h 甚至 1h 就可以实现这道题的正解,毕竟这种树上路径抽离的题目自己以前接触过。岁月不饶人,放在现在是不会那么快的就想到了。

代码

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 1e5 + 10;
  4. #define gc getchar()
  5. inline int read() {
  6. int x = 0; char c = gc;
  7. while(c < '0' || c > '9') c = gc;
  8. while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = gc;
  9. return x;
  10. }
  11. int now = 1, n, q;
  12. int head[N];
  13. struct Node {
  14. int v, nxt;
  15. } G[N << 1];
  16. void Add(int u, int v) {
  17. G[now].v = v, G[now].nxt = head[u]; head[u] = now ++;
  18. }
  19. int son[N], size[N], topp[N], deep[N], fa[N];
  20. int tree[N], tjs;
  21. int a[10];
  22. void Dfs1(int u, int f, int dep) {
  23. deep[u] = dep;
  24. size[u] = 1;
  25. fa[u] = f;
  26. for(int i = head[u]; ~ i; i = G[i].nxt) {
  27. int v = G[i].v;
  28. if(v != fa[u]) {
  29. Dfs1(v, u, dep + 1);
  30. size[u] += size[v];
  31. if(size[son[u]] < size[v]) son[u] = v;
  32. }
  33. }
  34. }
  35. void Dfs2(int u, int tp) {
  36. tree[u] = ++ tjs;
  37. topp[u] = tp;
  38. if(!son[u]) return ;
  39. Dfs2(son[u], tp);
  40. for(int i = head[u]; ~ i; i = G[i].nxt) {
  41. int v = G[i].v;
  42. if(v != fa[u] && v != son[u]) Dfs2(v, v);
  43. }
  44. }
  45. struct Seg {
  46. int l, r;
  47. } seg[N * 4];
  48. int segjs;
  49. void Lca(int x, int y) {
  50. if(x == y) return ;
  51. int tp1 = topp[x], tp2 = topp[y];
  52. while(tp1 != tp2) {
  53. if(deep[tp1] < deep[tp2]) swap(x, y), swap(tp1, tp2);
  54. seg[++ segjs].l = tree[tp1], seg[segjs].r = tree[x];
  55. x = fa[tp1];
  56. tp1 = topp[x];
  57. }
  58. if(tree[x] == tree[y]) return ;
  59. if(tree[x] < tree[y]) swap(x, y);
  60. seg[++ segjs].l = tree[y] + 1;
  61. seg[segjs].r = tree[x];
  62. }
  63. bool Cmp(Seg a, Seg b) {
  64. if(a.l == b.l) return a.r < b.r;
  65. else return a.l < b.l;
  66. }
  67. Seg A[N * 4];
  68. int ansjs;
  69. int main() {
  70. n = read();
  71. for(int i = 1; i <= n; i ++) head[i] = -1;
  72. for(int i = 1; i <= n - 1; i ++) {
  73. int u = read(), v = read();
  74. Add(u, v), Add(v, u);
  75. }
  76. Dfs1(1, 0, 0);
  77. Dfs2(1, 1);
  78. q = read();
  79. for(; q; q --) {
  80. int s = read();
  81. segjs = 0;
  82. for(int i = 1; i <= s; i ++) a[i] = read();
  83. for(int i = 1; i <= s; i ++) {
  84. for(int j = i + 1; j <= s; j ++) {
  85. Lca(a[i], a[j]);
  86. }
  87. }
  88. if(segjs == 0) {
  89. cout << 0 << "\n";
  90. continue;
  91. }
  92. sort(seg + 1, seg + segjs + 1, Cmp);
  93. ansjs = 1;
  94. A[1].l = seg[1].l, A[1].r = seg[1].r;
  95. for(int i = 2; i <= segjs; i ++) {
  96. if(seg[i].l <= seg[i - 1].r) {
  97. A[ansjs].r = max(A[ansjs].r, seg[i].r);
  98. } else {
  99. ansjs ++;
  100. A[ansjs].l = seg[i].l, A[ansjs].r = seg[i].r;
  101. }
  102. }
  103. int ans = 0;
  104. for(int i = 1; i <= ansjs; i ++) {
  105. if(A[i].l == A[i].r && A[i].l == 0) continue;
  106. ans += A[i].r - A[i].l + 1;
  107. }
  108. cout << ans << "\n";
  109. }
  110. return 0;
  111. }

T3 区间选择

输入 m 个区间,你希望从中选择尽量多的区间,使得两两不相交,也不能有公共端点。
假设输入的区间的 xi 和 yi 是 1 到 n 之间的整数,输入的区间不能有重复的。
输入的区间是没有顺序的(即输入[1,1] [2, 2] 和输入[2,2] [1, 1] 算作一种方案)
问有多少种选择这 m 个区间的方式,使得原问题的解,答案为 l 。
因为方案数很大,输出对 10007 取模的结果即可。
其中 n <= 50

得分分析:
鄙人只会 30 分暴力,现在的水平调试了 1h
啊! 真笨。
不过有早就该收获的新收获。

代码

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 1e5 + 10;
  4. int n, m, l_;
  5. struct Node {
  6. int l, r;
  7. } a[N];
  8. int js;
  9. bool vis[N];
  10. int ans;
  11. Node b[N];
  12. int tot;
  13. bool Cmp(Node aa, Node bb) {
  14. if(aa.l == bb.l) {
  15. return aa.r < bb.r;
  16. }
  17. return aa.l < bb.l;
  18. }
  19. int f[N];
  20. bool Check() {
  21. tot = 0;
  22. for(int i = 1; i <= js; i ++) {
  23. if(vis[i] == 1) {
  24. b[++ tot] = a[i];
  25. }
  26. }
  27. sort(b + 1, b + tot + 1, Cmp);
  28. for(int i = 1; i <= tot; i ++) f[i] = 1;
  29. for(int i = 2; i <= tot; i ++) {
  30. for(int j = 1; j < i; j ++) {
  31. if(b[i].l > b[j].r) {
  32. f[i] = max(f[i], f[j] + 1);
  33. }
  34. }
  35. }
  36. int seg = 0;
  37. for(int i = 1; i <= tot; i ++) seg = max(seg, f[i]);
  38. if(seg == l_) return 1;
  39. return 0;
  40. }
  41. void Dfs(int x, int step) {
  42. if(step == m) {
  43. if(Check()) ans ++;
  44. return ;
  45. }
  46. int i = x + 1;
  47. if(i == js + 1) return ;
  48. vis[i] = 1;
  49. Dfs(i, step + 1);
  50. vis[i] = 0;
  51. Dfs(i, step);
  52. }
  53. int main() {
  54. cin >> n >> m >> l_;
  55. for(int i = 1; i <= n; i ++) {
  56. for(int j = i; j <= n; j ++) {
  57. a[++ js].l = i;
  58. a[js].r = j;
  59. }
  60. }
  61. Dfs(0, 0);
  62. cout << ans;
  63. return 0;
  64. }

总结:如果只写暴力可以在 20min + 20min + 60min = 100min 得到 70 + 30 + 30 = 130 分。
稍加思考可以在 120min + 30min + 60min = 3.5h 得到 100 + 30 + 30 = 160 分
目前的能力极限: 100 + 100 + 30 = 230 分
总体来说,个人认为难度 > noip2018 Day1

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