2024 ICPC National Invitational Collegiate Programming Contest, Wuhan Site

2024 ICPC National Invitational Collegiate Programming Contest, Wuhan Site

I. Cyclic Apple Strings

题意:给定一个01字符串,每次操作可以将这个字符串向左循环移动任意次数,求让这个字符串变成有序的需要最少几次操作

思路:每次只能减少最右边的不和有边界相邻的一个1的长块,每次删去最右边的即可

void solve(){
    string s;
    cin >> s;
    int ans = 0, idx = s.size() - 1;
    while(idx >= 0 && s[idx] == '1') idx --;
    for(idx; idx >= 0; idx --){
        if(s[idx] == '1' && s[idx + 1] == '0') ans ++;
    }
    cout << ans << '\n';
}

 K. Party Games

题意:给定 n 个整数从左到右排成一列,每次可以从两端拿走其中一个数字,如果数字被拿完了或者剩余的数字的异或和为0,则当前操作无法进行,就失败了

思路:打表发现 n % 4 == 0 || n % 4 == 1 的情况下 Fluttershy 必胜,否则必败

void solve(){
    ll n;
    cin >> n;
    n %= 4;
    if(n == 1 || n == 0) cout << "Fluttershy\n";
    else cout << "Pinkie Pie\n"; 
}

B. Countless Me

题意:给定一个数组,每个数组的值可以随便分配,求最后的最小或运算的和

思路:从高位往低位贪心即可,如果在当前位的后面放满而且不够,那么放在当前位是最好的选择,注意这里的位数不要太大

void solve(){
    ll n, x, sum = 0;
    cin >> n;
    for(int i = 1; i <= n; i ++){
        cin >> x;
        sum += x;
    }
    ll ans = 0;
    for(int i = 30; i >= 0; i --){
        if(sum > ((1ll << i) - 1) * n){
            ll num = min(n, sum / (1ll << i));
            sum -= num * (1ll << i);
            ans += 1ll << i;
        }
    }
    cout << ans << '\n';
}

F. Custom-Made Clothes

题意:给定一个n * n 的矩阵,矩阵的每个点大于等于它左边的和上边的点(如果存在),每次可以查询一个点的数字是不是小于等于给定的询问值,在50000次查询中得出第k大的数字

思路:看50000次查询能猜到复杂度应该是2nlogn级别,二分答案,每次查询从左下角开始,如果小于等于mid,往上移动,否则把这一列上面的数字的个数全部加上,否则左移,每次查询的次数不超过2*n,总计2nlogn次查询得出答案

int n, k;
int ask(int x, int y, int z){
    cout << "? " << x << ' ' << y << ' ' << z << endl;
    int ans;
    cin >> ans;
    return ans;
}

bool check(int x){
    int sum = 0;
    int a = n, b = 1;
    while(a >= 1 && b <= n){
        int ans = ask(a, b, x);
        if(ans == 1){
            sum += a;
            b ++;
        }
        else{
            a --;
        }
    }
    if(sum >= k) return true;
    else return false;
}
void solve(){
    cin >> n >> k;
    k = n * n - k + 1;
    int l = 1, r = n * n;
    while(l < r){
        int mid = l + r >> 1;
        if(check(mid)) r = mid;
        else l = mid + 1;
    }
    cout << "! " << l << endl;
}

D. ICPC

题意:有 n 个点,每个点的价值都是 ai 并且每个点被拿走之后价值变为0,求从每个点开始移动次数为 1~2 * n 能获得的最大价值

思路:很容易想到,从每个点出发之后最多折返一次,预处理出价值的前缀和,方便求前缀价值和,然后求出不折返的情况下能获得的最大价值,然后求出从每个点的左边折返过来的最大价值,同理求出右边,最后取max即可

ll n, a[N];
ll dp1[N][N * 2], dp2[N][N * 2];

void solve(){
    cin >> n;
    for(int i = 1; i <= n; i ++) cin >> a[i];
    for(int i = 1; i <= n; i ++) a[i] += a[i - 1];
    for(int i = 1; i <= n; i ++){
        for(int j = 1; j <= n * 2; j ++){
            int l = max(1, i - j), r = min(n, 1ll * i + j);
            dp1[i][j] = max(a[i] - a[l - 1], a[r] - a[i - 1]);
        }
    }//不折返的情况
    for(int i = 1; i <= n; i ++){
        for(int j = 1; j <= 2 * n; j ++){
            dp2[i][j] = max(dp2[i - 1][j - 1], dp1[i][j]);
        }
    } 
    for(int i = n; i >= 1; i --){
        for(int j = 1; j <= n * 2; j ++){
            dp1[i][j] = max(dp1[i][j], dp1[i + 1][j - 1]);
        }
    }
    ll ans = 0;
    for(int i = 1; i <= n; i ++){
        ll res = 0;
        for(int j = 1; j <= 2 * n; j ++){
            res ^= j * max(dp1[i][j], dp2[i][j]);
        }
        ans ^= (i + res);
    }
    cout << ans << '\n';
}

E. Boomerang

题意:有一棵树,开始时假消息从r点开始传播,每次从已传播到的点开始传播到相邻的点,云宝从t0开始辟谣,辟谣从ro点开始,每次从已辟谣的点传播到相邻的k个所有节点,求k为1~n的情况下每次任选r0的情况最早什么时候完成辟谣

思路:可以很容易发现,从直径中间开始辟谣是最好的,动态维护一个树直径,LCA即可,每次把下一层的点加入,当加入一个新的点的时候,直径只有三种情况,假设新加入的点是 x,原直径两端是u, v,那么新的直径只会是(u, v), (u, x), (v. x),  不断扩大树的直径,然后记录每一秒的树的直径,然后开始枚举秒数,最大不会超过n + t0,因为时间是随着 k 的增大逐渐递减,所以枚举秒数,当就理论上扩散的深度比直径/2大,ans--,否则输出ans

struct LCA{
    int n;
    vector<int> dep, vis;
    vector<vector<int>> fa;
    LCA(int _n) : n(_n), dep(_n + 10), vis(_n + 10), fa(n + 1, vector<int>(25, 0)) {}
    
    void init(auto edge[], int x){
        auto dfs =[&](auto && dfs, int u, int fath){
            if(vis[u]) return;
            vis[u] = 1;
            dep[u] = dep[fath] + 1;
            fa[u][0] = fath;
            for(int i = 1; i <= __lg(dep[u]); i ++){
                fa[u][i] = fa[fa[u][i - 1]][i - 1];
            }
            for(auto v : edge[u]){
                dfs(dfs, v, u);
            }
        };
        dfs(dfs, x, 0);
    }
    int query(int a, int b){
        if(dep[a] > dep[b]) swap(a, b);
        while(dep[a] != dep[b]) b = fa[b][__lg(dep[b] - dep[a])];
        if(a == b) return a;
        for(int k = __lg(dep[a]); k >= 0; k --){
            if(fa[a][k] != fa[b][k]){
                a = fa[a][k], b = fa[b][k];
            }
        }
        return fa[a][0];
    }
    int dist(int u, int v){
        return dep[u] + dep[v] - 2 * dep[query(u, v)];
    }
};
void solve(){
    int n;
    cin >> n;
    vector<int> edge[n + 1];
    for(int i = 1; i < n; i ++){
        int u, v;
        cin >> u >> v;
        edge[u].push_back(v);
        edge[v].push_back(u);
    }
    int r, t0;
    cin >> r >> t0;
    LCA lca(n);
    lca.init(edge, r);
    
    vector<array<int, 3>> dd(n + 1);
    dd[1] = {1, r, r};//d, u, v
    int d = 0, du = r, dv = r;
    queue<PII> q;
    q.push({1, r});
    while(q.size()){
        auto [deep, u] = q.front();
        q.pop();
        auto now = max(dd[deep], dd[deep - 1]);
        int ud = lca.dist(now[1], u) + 1, vd = lca.dist(now[2], u) + 1;
        if(ud > now[0]){
            now = {ud, u, now[1]};
        }
        if(vd > now[0]){
            now = {vd, u, now[2]};
        }
        dd[deep] = max(dd[deep], now);
        for(auto v : edge[u]){
            if(v == lca.fa[u][0]) continue;
            q.push({deep + 1, v});
        }
    }
    for(int i = 1; i <= n; i ++) dd[i][0] = max(dd[i][0], dd[i - 1][0]);
    int ans = n + t0;
    for(int i = 1; i <= n; i ++){
        while(dd[min(n, ans)][0] / 2 <= (ans - 1 - t0) * i){
            ans --;
        }
        cout << ans << ' ';
    }
}

M. Merge

题意:给定n个数字,每次可以把绝对值差为1的两个数字合并,求最终数字任意排序后最大字典序情况

思路:由于是求字典序,肯定优先得到最大的数字,由于是绝对值差为1的两个数字的合并,所以一个为奇数,一个为偶数,每次取出最大的偶数合并,要不和x + 1合并,要不和x - 1合并,递归的取合并数字,如果需要的数字已经存在了,那就优先合成进去,然后再用小数字合成,可以发现, 奇数可以用一个奇数一个偶数合成,偶数除非本身就存在,否则无法合成,用map记录数字的数量来递归的合成即可

void solve(){
    ll n;
    cin >> n;
    map<ll, ll> mp; 
    multiset<ll> even;
    for(int i = 1; i <= n; i ++){
        ll x;
        cin >> x;
        mp[x] ++;
        if(x % 2 == 0) even.insert(x);
    }
    
    auto check =[&](auto && check, ll x) -> bool{
        if(mp[x]){
            mp[x] --;
            return true;
        }
        ll x2 = x / 2, y2 = x2 + 1;
        if(y2 % 2 == 0) swap(x2, y2);
        if(!mp[x2]) return false;
        else{
            mp[x2] --;
            if(check(check, y2)){
                return true;
            }
            else{
                mp[x2] ++;
                return false;
            }
        }
    };

    vector<ll> ans;
    while(even.size()){
        auto x = *even.rbegin();
        even.erase(even.find(x));
        if(!mp[x]) continue;
        mp[x] --;
        if(check(check, x + 1)){
            mp[x * 2 + 1] ++;        
        }
        else if(check(check, x - 1)){
            mp[x * 2 - 1] ++;        
        }
        else{
            ans.push_back(x);            
        }
    }
    for(auto &[x, y] : mp){
        for(int i = 1; i <= y; i ++) ans.push_back(x);
    }
    cout << ans.size() << '\n';
    sort(ans.begin(), ans.end(), greater<ll>());
    for(auto x : ans) cout << x << ' ';
}

 

posted @ 2024-05-14 18:26  Rosmontis_L  阅读(28)  评论(0编辑  收藏  举报