CEOI 2022 Day 1 题解
好像题都不难,可惜我 C 做了 3h,主要格式错了,是必须按照他的格式!!先读入才能输出。
A. Abracadabra
考虑归并排序有这样一个性质,考虑 \(a_i > a_{i+1}\),那么一旦 \(a_i\) 被扔进去,\(a_{i+1}\) 会紧跟着被扔进去。
那么可以将 \(a\) 按照前缀 \(\max\) 分段,每个前缀 \(\max\) 为开头,管辖后面一段 \(<\) 它的。
这样每次归并相当于就是对着这个前缀 \(\max\) 做了一次排序,段内不变。
考虑每次相当于是从 \(n / 2\) 处劈开,然后后半部分新生成了几个前缀 \(\max\)。
我们考虑 \(i\) 一旦是前缀 \(\max\) 一直都是前缀 \(\max\),所以这个过程不到 \(n\) 次其实也就不会变了,所以每次可以暴力找到新生成的前缀 \(\max\),然后插入到当前序列中。
仔细考虑一下事实上就是以前缀 \(\max\) 为值排序,可以开一颗权值线段树,然后 \(i\) 位置存如果 \(i\) 是前缀 \(\max\) 的段长度。然后每次可以找 \(n / 2\) 所在的段,如果不是最后一个就暴力分裂,每次找一些新段可以 \(\log\) 或者 \(O(1)\)(好像单调栈预处理一下就好了,可惜我 sb 写了个 st 表)。
时间复杂度 \(O(n \log n + q \log n)\)。
// 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
考虑建出表达式树,考虑 \(i\) 能不能成为答案,枚举 \(i\) 所在 \(?\) 的位置,把值离散化成 \(0, 1, 2\) 分别表示 \(<i\),\(=i\),\(>i\)。然后 \(i\) 从叶子往跟走,如果遇到 \(\max\),那么另一边儿子必须是 \(0\),如果是 \(\min\) 就得是 \(2\)。
所以考虑 \(f_{u, i}\) 表示 \(u\) 子树算出来答案是 \(i\),只填 \(0 / 2\),需要 \(0\) 的个数的集合,可以证明这个集合是个区间。证明:具体考虑转移的时候,归纳儿子是区间,比如 \(\max\) 时,这里是 \(0\) 就是俩都是 \(0\),算出来是俩区间的叠加也是区间;如果是 \(1\) 就是枚举那边是 \(2\) 然后区间往右平移另一边的 \(?\) 大小,讨论一下发现俩区间肯定有交(比如这俩子树 \(?\) 大小分别是 \(A, B\),俩区间分别是 \([x, y + B], [u, v + A]\),那么左边的区间肯定过 \(A\),如果 \(v < A\) 现在肯定过 \(A\),如果原来 \(u>A\) 那也不超过 \(B\),肯定有交。),那么并也是区间了。
那么考虑 \(h_i\) 是从根到 \(i\) 这个位置的问号路上的 \(f\) 叠加起来的结果,如果 \(i - 1\) 属于这个区间那么 \(i\) 就是可行的,区间覆盖一下就行了。
复杂度 \(O(n)\)。
// 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
考虑就选树 \(1\) 的 \(dfs\) 序的前 \(K\) 个作为集合,这样树 \(1\) 的虚树没有其他点!
然后你问的问题可以还原这棵树相当于是连边 \((l, a), (l,b)\) ,只有虚树构成的点的图联通。
考虑暴力一点把 \(2\) 的虚树每条边都问了,这样就是 \(2K - 2\) 了。
然后你发现你没有利用上还能知道到 \(l\) 深度这件事。
考虑 dfn 排序建虚树的那个过程,会调用 \(K - 1\) 次 \(lca\),事实上你只要把调用的 \(lca(a, b)\) 当做询问 \(a, b\) 就行了(就是相邻 \(dfn\))!
你考虑在树 \(2\) 上,归纳之前的边是知道的,然后你问了相邻两个 \(dfn\) 的 \(lca\),如果他们祖先关系也行,如果形成的新的虚点,这俩点也向虚点连边了!在树 \(1\) 上,考虑每次至少是有 \(i\) 向之前的点连边,这样那个图也是联通的!
// 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;
}