CF2152B Catching the Krug
废物hdh
如果一开始 K 和 D 在同一行或者同一列,那么 K 的逃跑方向唯一,D 抓住它的时间就是到那个方向的边界的切比雪夫距离。否则,就是两个逃跑方向的切比雪夫距离取 \(\max\)。
Code
#include <bits/stdc++.h>
using namespace std;
int n, rk, ck, rd, cd;
void solve(){cin >> n >> rk >> ck >> rd >> cd;int p = (rd < rk ? n - rd : rd), q = (cd < ck ? n - cd : cd);if(rk == rd) cout << q << '\n';else if(ck == cd) cout << p << '\n';else cout << max(p, q) << '\n';
}
int main(){cin.tie(nullptr)->sync_with_stdio(0);int T; cin >> T;while(T--) solve();return 0;
}
CF2152C Tripple Removal
判断一个区间是否是 01 交替根本不需要线段树,维护一个 \(c_i = a_i \oplus a_{i - 1}\) 然后看看区间内是否为全 1 即可。
赛时的 sb 代码。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 8e5 + 5;
#define ls(p) p << 1
#define rs(p) p << 1 | 1
int lp[N], rp[N], fl[N], a[N], n, sum[N], q;
void pushup(int p){fl[p] = 0;if(fl[ls(p)] && fl[rs(p)]) fl[p] = rp[ls(p)] ^ lp[rs(p)];lp[p] = lp[ls(p)], rp[p] = rp[rs(p)];
}
void build(int p, int pl, int pr){if(pl == pr) return lp[p] = rp[p] = a[pl], fl[p] = 1, void();int mid = (pl + pr) >> 1;build(ls(p), pl, mid);build(rs(p), mid + 1, pr);pushup(p);
}
vector<int> arr;
void get(int p, int pl, int pr, int L, int R){if(L <= pl && R >= pr) return arr.emplace_back(p), void();int mid = (pl + pr) >> 1;if(L <= mid) get(ls(p), pl, mid, L, R);if(R > mid) get(rs(p), mid + 1, pr, L, R);
}
bool check(int L, int R){arr.clear();get(1, 1, n, L, R);int lst = arr.front();if(!fl[lst]) return 0;for(int i = 1; i < arr.size(); ++i){int p = arr[i];if(!fl[p]) return 0;if(!(rp[lst] ^ lp[p])) return 0;lst = p;}return 1;
}
void solve(){cin >> n >> q;for(int i = 1; i <= n; ++i){cin >> a[i];sum[i] = sum[i - 1] + a[i];}build(1, 1, n);while(q--){int l, r; cin >> l >> r;int num1 = sum[r] - sum[l - 1], num0 = (r - l + 1) - num1;if(num0 % 3 || num1 % 3) cout << -1 << '\n';else{cout << (r - l + 1) / 3 + check(l, r) << '\n';}}
}
int main(){cin.tie(nullptr)->sync_with_stdio(0);int T; cin >> T;while(T--) solve();return 0;
}
CF2152D Division Versus Addition
基础答案是 \(\sum_{i = l}^{r} \lfloor \log_2{a_i} \rfloor\)。
只要 \(\operatorname{popcount}(x) \ge 2\),那么 R 都可以让答案 +1,要特别注意的是,对于 \(\operatorname{popcount}(x) = 2 \land x \text{ is odd}\) 的话,P 可以优先把他右移一位然后 R 就没办法了,这种是要争抢的资源,所以对于这种要除以二上取整从答案减去。一些前缀和维护就行。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 2.5e5 + 5;
int a[N], b[N], c[N], d[N], n, q;
void solve(){cin >> n >> q;for(int i = 1; i <= n; ++i){cin >> a[i];b[i] = b[i - 1] + __lg(a[i]);int cnt = 0;for(int j = 0; j < 32; ++j){if(a[i] & (1 << j)) ++cnt; }d[i] = d[i - 1];if(cnt == 2 && (a[i] & 1)) d[i]++;c[i] = c[i - 1] + (cnt >= 2);}while(q--){int l, r;cin >> l >> r;cout << b[r] - b[l - 1] + c[r] - c[l - 1] - (d[r] - d[l - 1] + 1) / 2 << '\n';}
}
int main(){cin.tie(nullptr)->sync_with_stdio(0);int T; cin >> T;while(T--) solve();return 0;
}
CF2152E Monotone Subsequence
清新交互题。第一次先询问 \(1 \sim n\) 的所有数,然后返回一个序列。如果这个序列长度 \(\ge n + 1\) 直接输出,如果没有就把他们都删掉,询问剩下的序列。重复 \(n\) 次。
如果这 \(n\) 次询问中,有任何一次返回的序列 \(\ge n + 1\) 了,就直接输出。否则进行完 \(n\) 次之后一定还剩一些数(至少一个)。对于一个第 \(i\) 次删除的数,找到它之前第一个第 \(i - 1\) 次删除的数,肯定是被它挡住了。由于我们删了 \(n + 1\) 次,所以把刚刚跳的过程反过来,就能找到长度为 \(n + 1\) 的一个递减序列。
这个构造也给出了一种题目中定理的证明方法。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e2 + 5;
set<int> s;
int n, a[N * N];
void solve(){cin >> n;for(int i = 1; i <= n * n + 1; ++i) s.insert(i);int cnt = 0;while(!s.empty()){cout << "? " << s.size() << ' ';for(int x : s) cout << x << ' ';cout << endl;int k; cin >> k;vector<int> tmp;for(int i = 1; i <= k; ++i){int o; cin >> o;tmp.emplace_back(o);} if(k >= n + 1){cout << "! ";for(int i = 0; i < n + 1; ++i) cout << tmp[i] << ' ';cout << endl;return;}++cnt;for(int x : tmp){s.erase(x);a[x] = cnt; }if(cnt == n) break; }for(int x : s) a[x] = n + 1;s.clear();cout << "! ";vector<int> ans;for(int i = 1; i <= n * n + 1; ++i){if(a[i] == n + 1){int p = i;ans.emplace_back(p);for(int j = n; j >= 1; --j){int o = p - 1;while(a[o] != j) --o;p = o;ans.emplace_back(p);}break;}}reverse(ans.begin(), ans.end());for(int x : ans) cout << x << ' ';cout << endl;
}
int main(){int T; cin >> T;while(T--) solve();return 0;
}
CF2152F Tripple Attack
最后要找的这个子序列要满足 \(a_{i + 2} - a_i > z\)。
我们选取 \((l, l + 1)\) 作为序列的两个开头肯定不劣。先考虑单个跳 \(>z\) 的情况,我们可以让 \(l\) 和 \(l + 1\) 分别往后跳,直到他们在 \(x\) 处相遇,类似树上 lca,这期间跳的步数(子序列长度)和跳到哪了都是容易求的。当他们在 \(x\) 处相遇时,我们强制把更靠后的那个引到 \(x + 1\),这样又变成了一个 \((x, x + 1)\) 向后跳的问题。
那么我们对这个结构在进行倍增(也就是 \((l, l + 1) \to (x , x + 1) \cdots\)),知道跳出边界。最后再单个往后跳肯定不会在 \(r\) 之前相交的。
注意一下当 \(l\) 直接跳到 \(l + 1\) 时的特判。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 2.5e5 + 5;
int a[N], f[N][21], g[N][21], dep[N], num[N][21], n, z;
int get(int p, int &nxt){if(p == n) return nxt = p + 1, 1;int x = p, y = p + 1;if(f[x][0] == y){ return nxt = y, 1; }for(int i : {0, 1, 2}){if(dep[f[x][i]] >= dep[y]) x = f[x][i];}for(int i = 19; i >= 0; --i){if(f[x][i] != f[y][i])x = f[x][i], y = f[y][i];}return nxt = f[x][0], dep[p] + dep[p + 1] - 2 * dep[nxt];
}
int query(int l, int r){int res = 0;for(int i = 19; i >= 0; --i){if(g[l][i] <= r){res += num[l][i];l = g[l][i];}}if(l == r) return res + 1;res += 2;int x = l, y = l + 1;for(int i = 19; i >= 0; --i){if(f[x][i] <= r){res += (1 << i);x = f[x][i];}if(f[y][i] <= r){res += (1 << i);y = f[y][i];}}return res;
}
void solve(){cin >> n >> z;for(int i = 1; i <= n; ++i) cin >> a[i];int j = 1;for(int i = 1; i <= n; ++i){while(j <= n && a[j] <= a[i] + z) ++j;f[i][0] = j; }for(int i = n; i >= 1; --i) dep[i] = dep[f[i][0]] + 1;for(int i = 0; i <= 19; ++i) g[n + 1][i] = f[n + 1][i] = n + 1;for(int j = 1; j <= 19; ++j){for(int i = 1; i <= n; ++i){f[i][j] = f[f[i][j - 1]][j - 1];}}for(int i = 1; i <= n; ++i){ num[i][0] = get(i, g[i][0]);// cout << i << ' ' << g[i][0] << ' ' << num[i][0] << '\n';}for(int j = 1; j <= 19; ++j){for(int i = 1; i <= n; ++i){num[i][j] = num[i][j - 1] + num[g[i][j - 1]][j - 1];g[i][j] = g[g[i][j - 1]][j - 1];}}int q; cin >> q;while(q--){int l, r; cin >> l >> r;if(l == n) cout << "1\n";else cout << query(l, r) << '\n';}
}
int main(){cin.tie(nullptr)->sync_with_stdio(0);int T; cin >> T;while(T--) solve();return 0;
}
CF2152G Query Jungle
一个括号序的应用。括号序是 dfs 进来的时候加左括号,出去的时候加右括号。区别欧拉序是每进来一次都加一次。
题目要求的是有多少个 \(a_i = 1\) 的点的子树内没有任何 1。那么我们只看 1 的那些点形成的括号序,等价于问有多少个 ()。
考虑线段树。维护区间最左/右边的 \(a_i = 0/1\) 的点是左括号还是右括号,和 \(a_i = 0/1\) 的答案,这样区间反转就是交换 0 / 1 了。这种区间反转的都得对称的维护一些东西。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 5;
#define ls(p) p << 1
#define rs(p) p << 1 | 1
int b[N], a[N], n, c[N], l[N], r[N], tsp, tag[N * 4], lp[N * 4][2], rp[N * 4][2], ans[N * 4][2];
vector<int> e[N];
void dfs(int u, int fa){b[++tsp] = 0;c[tsp] = u;l[u] = tsp;for(int v : e[u]){ if(v != fa) dfs(v, u);}b[++tsp] = 1;c[tsp] = u;r[u] = tsp;
}
void pushup(int p){for(int j : {0, 1}){ans[p][j] = ans[ls(p)][j] + ans[rs(p)][j] + (rp[ls(p)][j] == 0 && lp[rs(p)][j] == 1);lp[p][j] = (lp[ls(p)][j] == -1 ? lp[rs(p)][j] : lp[ls(p)][j]);rp[p][j] = (rp[rs(p)][j] == -1 ? rp[ls(p)][j] : rp[rs(p)][j]);}
}
void addtag(int p){tag[p] ^= 1;swap(lp[p][0], lp[p][1]);swap(rp[p][0], rp[p][1]);swap(ans[p][0], ans[p][1]);
}
void pushdown(int p){if(tag[p]){addtag(ls(p));addtag(rs(p));tag[p] = 0;}
}
void build(int p = 1, int pl = 1, int pr = 2 * n){ans[p][0] = ans[p][1] = tag[p] = 0;if(pl == pr){int vl = a[c[pl]];lp[p][vl] = rp[p][vl] = b[pl];lp[p][vl ^ 1] = rp[p][vl ^ 1] = -1;return;}int mid = (pl + pr) >> 1;build(ls(p), pl, mid);build(rs(p), mid + 1, pr);pushup(p);
}
void flip(int L, int R, int p = 1, int pl = 1, int pr = 2 * n){if(L <= pl && R >= pr) return addtag(p);int mid = (pl + pr) >> 1;pushdown(p);if(L <= mid) flip(L, R, ls(p), pl, mid);if(R > mid) flip(L, R, rs(p), mid + 1, pr);pushup(p);
}
void solve(){cin >> n;for(int i = 1; i <= n; ++i) e[i].clear(), cin >> a[i];for(int i = 1; i < n; ++i){int u, v; cin >> u >> v;e[u].emplace_back(v);e[v].emplace_back(u);}tsp = 0;dfs(1, 0);build();assert(tsp == 2 * n);cout << ans[1][1] << '\n';int q; cin >> q;while(q--){int u; cin >> u;flip(l[u], r[u]);cout << ans[1][1] << '\n';}
}
int main(){cin.tie(nullptr)->sync_with_stdio(0);int T; cin >> T;while(T--) solve();return 0;
}