CEOI 2022 Day 1 题解

好像题都不难,可惜我 C 做了 3h,主要格式错了,是必须按照他的格式!!先读入才能输出。

A. Abracadabra

考虑归并排序有这样一个性质,考虑 ai>ai+1,那么一旦 ai 被扔进去,ai+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(nlogn+qlogn)

// 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

所以考虑 fu,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,肯定有交。),那么并也是区间了。

那么考虑 hi 是从根到 i 这个位置的问号路上的 f 叠加起来的结果,如果 i1 属于这个区间那么 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

考虑就选树 1dfs 序的前 K 个作为集合,这样树 1 的虚树没有其他点!

然后你问的问题可以还原这棵树相当于是连边 (l,a),(l,b) ,只有虚树构成的点的图联通。

考虑暴力一点把 2 的虚树每条边都问了,这样就是 2K2 了。

然后你发现你没有利用上还能知道到 l 深度这件事。

考虑 dfn 排序建虚树的那个过程,会调用 K1lca,事实上你只要把调用的 lca(a,b) 当做询问 a,b 就行了(就是相邻 dfn)!

你考虑在树 2 上,归纳之前的边是知道的,然后你问了相邻两个 dfnlca,如果他们祖先关系也行,如果形成的新的虚点,这俩点也向虚点连边了!在树 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;
}
posted @   DMoRanSky  阅读(565)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示