CEOI 2022 Day 1 题解
好像题都不难,可惜我 C 做了 3h,主要格式错了,是必须按照他的格式!!先读入才能输出。
A. Abracadabra
考虑归并排序有这样一个性质,考虑 ,那么一旦 被扔进去, 会紧跟着被扔进去。
那么可以将 按照前缀 分段,每个前缀 为开头,管辖后面一段 它的。
这样每次归并相当于就是对着这个前缀 做了一次排序,段内不变。
考虑每次相当于是从 处劈开,然后后半部分新生成了几个前缀 。
我们考虑 一旦是前缀 一直都是前缀 ,所以这个过程不到 次其实也就不会变了,所以每次可以暴力找到新生成的前缀 ,然后插入到当前序列中。
仔细考虑一下事实上就是以前缀 为值排序,可以开一颗权值线段树,然后 位置存如果 是前缀 的段长度。然后每次可以找 所在的段,如果不是最后一个就暴力分裂,每次找一些新段可以 或者 (好像单调栈预处理一下就好了,可惜我 sb 写了个 st 表)。
时间复杂度 。
// Skyqwq #include <bits/stdc++.h> #define pb push_back #define fi first #define se second #define mp make_pair using namespace std; typedef pair<int, int> PII; typedef long long LL; template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; } template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; } template <typename T> void inline read(T &x) { int f = 1; x = 0; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); } while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar(); x *= f; } const int N = 2e5 + 5, M = 1e6 + 5, L = 18; int n, q, a[N], la[N], pos[N]; int sz[N]; int st[L][N], g[N]; void bdST() { g[0] = -1; for (int i = 1; i <= n; i++) st[0][i] = a[i], g[i] = g[i >> 1] + 1; for (int j = 1; j <= g[n]; j++) { for (int i = 1; i + (1 << j) - 1 <= n; i++) { st[j][i] = max(st[j - 1][i], st[j - 1][i + (1 << (j - 1))]); } } } int qry(int l, int r) { int k = g[r - l + 1]; return max(st[k][l], st[k][r - (1 << k) + 1]); } int b[N]; int dat[N << 2]; #define ls (p << 1) #define rs (p << 1 | 1) void pu(int p) { dat[p] = dat[ls] + dat[rs]; } int ask(int p, int l, int r, int k) { if (l == r) { return a[pos[r] + k - 1]; } int mid = (l + r) >> 1; if (k <= dat[ls]) return ask(ls, l, mid, k); else return ask(rs, mid + 1, r, k - dat[ls]); } void chg(int p, int l, int r, int x, int y) { if (l == r) { dat[p] = sz[r]; return; } int mid = (l + r) >> 1; if (x <= mid) chg(ls, l, mid, x, y); else chg(rs, mid + 1, r, x, y); pu(p); } void upd(int x, int y) { sz[x] = y; chg(1, 1, n, x, y); } void fd(int x, int y) { for (int i = x; i <= y; i++) { int l = i, r = y; while (l < r) { int mid = (l + r + 1) >> 1; if (qry(i, mid) == a[i]) l = mid; else r = mid - 1; } upd(a[i], r - i + 1); i = r; } } bool div(int p, int l, int r, int k) { if (l == r) { if (sz[r] == k) { return 0; } else { fd(pos[r] + k, pos[r] + sz[r] - 1); upd(r, k); return 1; } } int mid = (l + r) >> 1; if (k <= dat[ls]) return div(ls, l, mid, k); else return div(rs, mid + 1, r, k - dat[ls]); } struct E{ int t, i, id; bool operator < (const E &b) const { return t < b.t; } } e[M]; int ans[M], now; void sh(int x) { while (now <= q && e[now].t <= x) { ans[e[now].id] = ask(1, 1, n, e[now].i); now++; } } void inline out() { for (int i = 1; i <= n; i++) cout << ask(1, 1, n, i) << " "; cout << endl; } int main() { read(n), read(q); for (int i = 1; i <= n; i++) read(a[i]), pos[a[i]] = i; bdST(); fd(1, n); for (int i = 1; i <= q; i++) { read(e[i].t), read(e[i].i), e[i].id = i; } sort(e + 1, e + 1 + q); int c = 0; now = 1; sh(c); //out(); while (1) { if (!div(1, 1, n, n / 2)) break; sh(++c); //out(); } sh(1e9); for (int i = 1; i <= q; i++) printf("%d\n", ans[i]); return 0; }
B. Homework
考虑建出表达式树,考虑 能不能成为答案,枚举 所在 的位置,把值离散化成 分别表示 ,,。然后 从叶子往跟走,如果遇到 ,那么另一边儿子必须是 ,如果是 就得是 。
所以考虑 表示 子树算出来答案是 ,只填 ,需要 的个数的集合,可以证明这个集合是个区间。证明:具体考虑转移的时候,归纳儿子是区间,比如 时,这里是 就是俩都是 ,算出来是俩区间的叠加也是区间;如果是 就是枚举那边是 然后区间往右平移另一边的 大小,讨论一下发现俩区间肯定有交(比如这俩子树 大小分别是 ,俩区间分别是 ,那么左边的区间肯定过 ,如果 现在肯定过 ,如果原来 那也不超过 ,肯定有交。),那么并也是区间了。
那么考虑 是从根到 这个位置的问号路上的 叠加起来的结果,如果 属于这个区间那么 就是可行的,区间覆盖一下就行了。
复杂度 。
// Skyqwq #include <bits/stdc++.h> #define pb push_back #define fi first #define se second #define mp make_pair using namespace std; typedef pair<int, int> PII; typedef long long LL; template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; } template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; } template <typename T> void inline read(T &x) { int f = 1; x = 0; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); } while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar(); x *= f; } const int N = 2e6 + 5, INF = 1e9; string str; vector<int> g[N]; int idx, op[N], s[N], top, n, tot, rt; PII I = mp(INF, -INF); PII inline mg(PII a, PII b) { return mp(min(a.fi, b.fi), max(a.se, b.se)); } PII inline add(PII a, PII b) { return mp(a.fi + b.fi, a.se + b.se); } PII inline apd(PII a, int b) { return mp(a.fi, a.se + b); } PII f[N][2]; int sz[N]; void dfs1(int u) { sz[u] = u <= n ? 1 : 0; if (u <= n) { f[u][0] = mp(1, 1); f[u][1] = mp(0, 0); return; } for (int v: g[u]) { dfs1(v); sz[u] += sz[v]; } int A = g[u][0], B = g[u][1]; if (op[u] == 0) { f[u][0] = mg(apd(f[A][0], sz[B]), apd(f[B][0], sz[A])); f[u][1] = add(f[A][1], f[B][1]); } else { f[u][1] = mg(apd(f[A][1], sz[B]), apd(f[B][1], sz[A])); f[u][0] = add(f[A][0], f[B][0]); } // cerr << u << " " << A << " " << B << " ----:::\n"; // for (int i = 0; i < 2; i++) { // cerr << f[u][i].fi << " " << f[u][i].se << " ---\n"; // } } PII ret = I; int c[N]; void dfs2(int u, PII now) { if (u <= n) { c[now.fi]++, c[now.se + 1]--; return; } for (int i = 0; i < 2; i++) { dfs2(g[u][i], add(now, f[g[u][i ^ 1]][op[u] == 0 ? 1 : 0])); } } int main() { cin >> str; for (char c: str) if (c == '?') ++n; idx = n; rt = idx + 1; for (int i = 0; i < str.size(); i++) { if (str[i] == '(') { s[++top] = ++idx; if (top > 1) g[s[top - 1]].pb(s[top]); op[idx] = str[i - 1] == 'n' ? 0 : 1; } else if (str[i] == ')') { --top; } else if (str[i] == '?') { ++tot; assert(top); g[s[top]].pb(tot); } } dfs1(rt); dfs2(rt, mp(0, 0)); int ans = 0; //cerr << ret.fi << " -- " << ret.se << endl; for (int i = 0; i < n; i++) { if (i) c[i] += c[i - 1]; if (c[i]) ans++; } printf("%d\n", ans); return 0; }
C. Prize
考虑就选树 的 序的前 个作为集合,这样树 的虚树没有其他点!
然后你问的问题可以还原这棵树相当于是连边 ,只有虚树构成的点的图联通。
考虑暴力一点把 的虚树每条边都问了,这样就是 了。
然后你发现你没有利用上还能知道到 深度这件事。
考虑 dfn 排序建虚树的那个过程,会调用 次 ,事实上你只要把调用的 当做询问 就行了(就是相邻 )!
你考虑在树 上,归纳之前的边是知道的,然后你问了相邻两个 的 ,如果他们祖先关系也行,如果形成的新的虚点,这俩点也向虚点连边了!在树 上,考虑每次至少是有 向之前的点连边,这样那个图也是联通的!
// Skyqwq #include <bits/stdc++.h> #define pb push_back #define fi first #define se second #define mp make_pair using namespace std; typedef pair<int, int> PII; typedef long long LL; template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; } template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; } // char buf[1<<23],*p1=buf,*p2=buf,obuf[1<<23],*O=obuf; // #define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++) template <typename T> void inline read(T &x) { int f = 1; x = 0; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); } while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar(); x *= f; } const int N = 1e6 + 5; vector<PII> t; struct G{ vector<int> g[N]; int sz[N], fa[N], dep[N], top[N], hson[N], dfn[N], dfncnt, pre[N]; int rt; void dfs1(int u) { sz[u] = 1; for (int v: g[u]) { if (v == fa[u]) continue; dep[v] = dep[u] + 1, fa[v] = u; dfs1(v); sz[u] += sz[v]; if (sz[v] > sz[hson[u]]) hson[u] = v; } } void dfs2(int u, int tp) { top[u] = tp; dfn[u] = ++dfncnt; pre[dfn[u]] = u; if (hson[u]) dfs2(hson[u], tp); for (int v: g[u]) { if (v == fa[u] || v == hson[u]) continue; dfs2(v, v); } } int lca(int x, int y) { while (top[x] != top[y]) { if (dep[top[x]] < dep[top[y]]) swap(x, y); x = fa[top[x]]; } if (dep[x] < dep[y]) swap(x, y); return y; } int s[N], tp; vector<int> e[N]; void insert(int x) { if (!tp) { s[++tp] = x; return; } int p = lca(x, s[tp]); t.pb(mp(x, s[tp])); while (tp > 1 && dep[s[tp - 1]] >= dep[p]) e[s[tp - 1]].pb(s[tp]), tp--; if (s[tp] != p) { e[p].pb(s[tp]); s[tp] = p; } s[++tp] = x; } int inline build(vector<int> &A) { tp = 0; sort(A.begin(), A.end(), [&] (int x, int y) {return dfn[x] < dfn[y]; }); for (int x: A) { insert(x); } for (int i = 1; i < tp; i++) e[s[i]].pb(s[i + 1]); return s[1]; } void init () { dfs1(rt); dfs2(rt, rt); } void add(int x, int y, int z) { h[x].pb(mp(y, z)); h[y].pb(mp(x, -z)); } vector<PII> h[N]; bool vis[N]; LL d[N]; void dfs4(int u) { vis[u] = 1; for (PII o: h[u]) { int v = o.fi, w = o.se; if (!vis[v]) { d[v] = d[u] + o.se; dfs4(v); } } } void bd(int u) { dfs4(u); } int D(int x, int y) { int p = lca(x, y); assert(vis[p] && vis[x] && vis[y]); return (LL)d[x] + d[y] - 2 * d[p]; } } t1, t2; int n, K, Q, T, F[N], ot; vector<int> w; // void dfs3(int u) { // vector<int> c; // for (int v: t2.e[u]) { // if (v == F[u]) continue; // F[v] = u; // dfs3(v); // c.pb(v); // } // if (c.size()) t.pb(mp(w[0], c[0])); // for (int i = 1; i < c.size(); i++) t.pb(mp(c[i], c[i - 1])); // } int main() { read(n), read(K), read(Q), read(T); for (int i = 1; i <= n; i++) { int f; read(f); if (f != -1) t1.g[f].pb(i), t1.g[i].pb(f); else t1.rt = i; } for (int i = 1; i <= n; i++) { int f; read(f); if (f != -1) t2.g[f].pb(i), t2.g[i].pb(f); else t2.rt = i; } t1.init(), t2.init(); for (int i = 1; i <= K; i++) { int u = t1.pre[i]; printf("%d", u); if (i != K) putchar(' '); w.pb(u); } puts(""); fflush(stdout); ot = t2.build(w); //dfs3(ot); // assert(t.size() <= Q); for (PII o: t) printf("? %d %d\n", o.fi, o.se); puts("!"); fflush(stdout); for (PII o: t) { int x = o.fi, y = o.se; int A, B, C, D; read(A), read(B), read(C), read(D); int l1 = t1.lca(x, y), l2 = t2.lca(x, y); t1.add(l1, x, A); t1.add(l1, y, B); t2.add(l2, x, C); t2.add(l2, y, D); } t1.bd(w[0]), t2.bd(w[0]); vector<PII> ans; for (int i = 1; i <= T; i++) { int x, y; read(x), read(y); ans.pb(mp(t1.D(x, y), t2.D(x, y))); } for (PII o: ans) printf("%d %d\n", o.fi, o.se); fflush(stdout); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通