CF2152

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 就没办法了,这种是要争抢的资源,所以对于这种要除以二上取整从答案减去。一些前缀和维护就行。
赛时到底在犯什么唐以为那种争抢形的资源只用减 1。

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;
}
posted @ 2025-11-23 15:05  Hengsber  阅读(2)  评论(0)    收藏  举报