LOADING

加载过慢请开启缓存 浏览器默认开启

2024ICPC武汉邀请赛

备战

更新至I、K、B、F、E、D、C

I.Cyclic Apple Strings

题意:给定一个 $01$ 串,每次操作可以选择一个连续段使该范围内字符连续左移若干次,求使得整串严格非递减的最少操作数。

解:显然统计从左到右需操作的段数即可。

Code
#include"bits/stdc++.h"
using namespace std;
#define pb(x) push_back(x)
typedef long long ll;
const int N = 2e5 + 10, MOD = 1e9 + 7;
int main(){
    string s; cin >> s;
    int n = s.size(), ans = 0; 
    s = " " + s; s += "1";
    for(int i = 1; i <= n; ++ i){
        if(s[i] == '1' && s[i + 1] == '0') ans ++;
    }
    cout << ans;
}

K.Party Games

题意:定义一个长度为 $n$ 的数组为 $1 - n$ 的排列,两个人轮流进行操作,每次操作可以从该数组的头部或尾部拿走一个数,当且仅当剩下的数组异或和为 $0$ 时判定当前操作人为输。

解:通过打表得

i = 1,    sum = 1
i = 2,    sum = 3
i = 3,    sum = 0
i = 4,    sum = 4
i = 5,    sum = 1
i = 6,    sum = 7
i = 7,    sum = 0
i = 8,    sum = 8
i = 9,    sum = 1
i = 10,    sum = 11
i = 11,    sum = 0
i = 12,    sum = 12
i = 13,    sum = 1
i = 14,    sum = 15
i = 15,    sum = 0
i = 16,    sum = 16

可以发现其中异或和 $sum$ 四个数一个周期,、且 $sum = 1$ 时通过拿走头数字先手必胜, $i\ mod\ 4 = 0$ 时通过拿走队尾数字先手必胜,其余情况先手必败。

Code

#include "bits/stdc++.h"
using namespace std;
#define pb(x) push_back(x)
typedef long long ll;
const int N = 2e5 + 10, MOD = 1e9 + 7;
int main(){
    int T; cin >> T;
    while(T --){
        int x; cin >> x;
        if(x % 4 == 0 || x % 4 == 1) cout << "Fluttershy\n";
        else cout << "Pinkie Pie\n";
    }
}

B.Countless Me

题意:给定长度为 $n$ 的 $a\ (a_i\le1e9)$ 数组,使用不超过 $n$ 次操作,每次操作选择两个不同的元素 $a_i, a_j$ 和一个不大于 $a_j$ 的 $x$ 使得 $a_i = a_i + x, a _j = a_j - x$。 问最终数组的
$按位或$ 后最小值为多少。

解:通过 $n$ 次操作我们可以使得数组变成任意的数组组合,且满足于数组的总和 $sum$ 不变。因此我们可以从高到低位判断是否需要在 $1<<i$ 这个位置上放数,一旦放数后即可尽量把 $n$ 个 $1<<i$ 填满即可。

Code
signed main(){
    int ans = 0, sum = 0;
    int n; cin >> n;
    for(int i = 1; i <= n; ++ i) {
        int x; cin >> x;
        sum += x;
    }
    for(int i = 30; i >= 0; -- i){
        int x = 1ll << i;
        if(sum > (x - 1) * n){
            int y = sum / x;
            sum -= 1ll * x * min(y, n);
            ans |= x;
        }
    }
    cout << ans;
}

F.Custom-Made Clothes

交互题

题意:存在一个 $n*n\ (n\le 1000)$ 的正整数矩阵,其中的正整数满足 $a_{i,j}\ge a_{i,j-1},\ a_{i,j}\ge a_{i - 1,j}$, 最多 $50000$ 次询问,每次询问 $i, j, x$ 返回 $a_{i,j}\le x$ 的结果,求问该矩阵中第 $k$ 大的值。

解:间接转化成求矩阵中第 $n * n - k$ 个小的值,可以二分这个答案 $x$, 并根据矩阵的特性每次通过 $n$ 次询问查找矩阵中小于 $x$ 的个数而更新二分边界。

Code
int n, k; 
bool ask(int x, int y, int s){
    cout << "? " << x << ' ' << y << ' ' << s << '\n';
    int xx; cin >> xx;
    return xx;
}
int ok(int mid){
    int sum = 0;
    for(int i = 1, j = n; i <= n; i ++){
        while(j >= 1 && !ask(i, j, mid)) j --;
        sum += j;
    }
    return sum;
}
int main(){
    cin >> n >> k;
    k = n * n - k + 1;
    // cout << k << '\n';
    int l = 1, r = n * n;
    while(l < r){
        int mid = l + r >> 1;
        if(k > ok(mid)) l = mid + 1;
        else r = mid; 
    }
    cout << "! " << l;
}

E.Boomerang

题意:给定一个 $n$ 大小的无向树,一个谣言起始点 $r$ 和 开始消除谣言的时间 $t_0$,存在谣言的结点每个时间单位向边上的结点扩散, $t_0$ 时间之后可以选择一个结点以个时间单位里 $k$ 的步长向边沿结点辟谣,问分别当 $k=1 \sim n$ 时辟完所有谣的最小时间单位。

解:通过根节点 $r$ 预处理求出经过每个时间单位树中被谣言覆盖的直径,随后对于每个 $k$ 二分查找花费最少时间辟谣的时间 $t$,此时答案就是 $t_0 + t$

Code
#include"bits/stdc++.h"
using namespace std;
#define pb(x) push_back(x)
typedef long long ll;
const int N = 2e5 + 10, MOD = 1e9 + 7;
vector g[N], g0[N];
int dist[N], f[N][30], lg[N], d[N];
void build(int u, int pre){
    f[u][0] = pre;
    dist[u] = dist[pre] + 1;
    g0[dist[u]].push_back(u);
    for(int i = 1; f[u][i - 1]; i ++){
        f[u][i] = f[f[u][i - 1]][i - 1];
    }
    for(auto v: g[u]){
        if(v != pre) build(v, u);
    }
}
int lca(int u, int v){
    if(dist[u] < dist[v]) swap(u, v);
    while(dist[u] > dist[v]) u = f[u][lg[dist[u] - dist[v]] - 1];
    if(u == v) return v;
    for(int i = lg[dist[u]] - 1; i >= 0; -- i){
        if(f[u][i] != f[v][i]){
            u = f[u][i];
            v = f[v][i];
        }
    }
    return f[u][0];
}
int get(int u, int v){
    int tt = lca(u, v);
    return dist[u] + dist[v] - 2 * dist[tt];
}
int main(){
    for(int i = 1; i <= N; ++ i){
        int x = i;
        while(x){
            lg[i] ++;
            x /= 2;
        }
    }
    int n, r, t0; cin >> n;
    for(int i = 1; i < n; ++ i){
        int u, v; cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    cin >> r >> t0;
    dist[0] = -1;
    build(r, 0);
    for(int i = 1; i <= n; ++ i){
        d[i] = d[i - 1];
        if(g0[i].size() != 0){
            int x = g0[i][0];
            int mx = d[i] + 1;
            for(int j = 1; j < g0[i].size(); ++ j){
                mx = max(mx, get(x, g0[i][j]));
            }
            d[i] = max(mx, d[i]);
        }
    }
    for(int i = 1; i <= n; ++ i){
        d[i] ++;
        // cout << d[i] << " \n"[i == n];
    }
    for(int i = n + 1; i <= n + n; ++ i) d[i] = d[i - 1];
    for(int k = 1; k <= n; ++ k){
        int l = 1, r = n;
        while(l < r){
            int mid = l + r >> 1;
            if(d[t0 + mid] / 2 <= 1ll * mid * k) r = mid;
            else l = mid + 1;
        }
        cout << t0 + l << " \n"[k == n];
    }
}
lca+二分

D.ICPC

题意:有一个长度为 $n$ 的餐桌每个位置有一个权值为 $a_i$ 的食物,初始位于 $s$ 处的小M每秒可以向左或向右移动一个单位并立即吃掉该食物,假设 $F[i][j]$ 表示初始位于位置 $i$ 的小M经过 $j$ 秒吃到权值和最大的值,那么求
$\oplus \ _{i=1}^n$ $(i+\oplus$ $\ _{j=1}^{2n}*F[i][j])$

解:从公式看出本题的重点在于求出 $F[i][j]$,而思考后显然移动的情况要么一直往一个方向,要么最多折返一次。

当一直往一个方向时 $F[i][j] = max(sum[i + j] - sum[i - 1], sum[i] - sum[i - j - 1])$, 此时求出直线运动的最大值。

当折返一次时,$F[i][j] = max(F[i+1][j-1], F[i-1][j-1])$, 由于转移过程中一直调用 $j-1$ 的状态,所以 $j$ 应在第一层枚举。

Code
#include"bits/stdc++.h"
using namespace std;
#define pb(x) push_back(x)
typedef long long ll;
const int N = 2e5 + 10, MOD = 1e9 + 7;
ll dp[5010][2 * 5010], sum[N];
int main(){
    int n; cin >> n;
    for(int i = 1; i <= n; ++ i){
        cin >> sum[i];
        sum[i] += sum[i - 1];
    }
    for(int i = 1; i <= n; ++ i){
        dp[i][0] = sum[i] - sum[i - 1];
        for(int j = 1; j <= n + n; ++ j){
            dp[i][j] = dp[i][j - 1];
            if(i + j <= n + n){
                dp[i][j] = max(dp[i][j],sum[i + j] - sum[i - 1]);
            }
            if(i - j >= 1){
                dp[i][j] = max(dp[i][j], sum[i] - sum[i - j - 1]);
            }
        }
    }
    for(int j = 1; j <= n + n; ++ j){
        for(int i = 1; i <= n; ++ i){
            if(i + 1 <= n)
                dp[i][j] = max(dp[i][j], dp[i + 1][j - 1]);
            if(i - 1 >= 1)
                dp[i][j] = max(dp[i][j], dp[i - 1][j - 1]);
        }
    }
    ll ans = 0;
    for(int i = 1; i <= n; ++ i){
        ll res = 0;
        for(int j = 0; j <= n + n; ++ j){
            // cout << dp[i][j] << " \n"[j == n + n];
            res ^= 1ll * j * dp[i][j];
        }
        ans ^= res + i;
    }
    cout << ans;
}

C:TreeBag and LIS

题意:给定一个数 $x(x\le 10^13)$, 要求构造一个长度不超过 $10^5$ 的数字序列,使得里面严格递增的所有最长子序列和为 $x$。

比如 $x=494$,可以构造$001243$,此时序列中$0124+0123+0124+0123=494$。

发现可以通过012012012012的摆法,使得和为 $(12*等差数列求和数量sum)$,通过在每一对 $”012”$ 后边放 “2”,并且从大到小凑这个 $sum$ 即可。当然这里的前提是将 $x$ 变成 $12$ 的倍数,这里通过暴力枚举三个数即可。
不用 $12$ 也可以,下面代码以13为例。

Code
int main(){
    ll x; cin >> x;
    string s = "";
    if(x == 0) s += '0';
    else if(x <= 99910){
        s += '0';
        while(x --) s += '1';
    }else {
        for(int a = 3; a < 10; ++ a){
            for(int b = a + 1; b < 10; ++ b){
                for(int c = b + 1; c < 10; ++ c){
                    int k = a * 100 + b * 10 + c;
                    if(x % 13 != 0 && (x - k) % 13 == 0){
                        s += '0' + a;
                        s += '0' + b;
                        s += '0' + c;
                        x -= k;
                    }
                }
            }
        }
        x /= 13;
        int t = 0;
        while(1ll * t * (t + 1) * (t + 2) / 6 <= x) t ++;
        t --;
        x -= 1ll * t * (t + 1) * (t + 2) / 6;
        vector p(t + 1);
        for(int i = t; i >= 1; -- i){
            ll d = 1ll * (1 + i) * i / 2;
            p[i] = x / d;
            x -= 1ll * d * p[i];        
        }
        for(int i = 1; i <= t; ++ i){
            s += "013";
            while(p[i] --) s += '3';
        }
    }
    cout << s;
}