[Codeforces Round #668 (Div. 2)]1405




竞赛首页 Codeforces Round #668 (Div. 2)

A - Permutation Forgery

题意

找到一个另一个序列不同于给出的序列,使的该序列得到的所有 a [ i − 1 ] + a [ i ] ( i > 1 ) a[i-1] + a[i](i > 1) a[i1]+a[i](i>1),排序后与给定的序列得到的 a [ i − 1 ] + a [ i ] ( i > 1 ) a[i-1] + a[i](i > 1) a[i1]+a[i](i>1) 排序后相同。
序列中的每个值是不同的

分析

其实可以发现,只要把给出的序列逆向输出,就可以满足所要的条件

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100 + 5;;
const ll mod = 1e9 + 7;

int a[maxn];

int main() {
    int T;
    scanf("%d", &T);
    while(T--) {
        int n;
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
        for(int i = n; i >= 1; --i)
            printf("%d%c", a[i], i == 1 ? '\n' : ' ');
    }
    return 0;
}


B - Array Cancellation

题意

给出长度为 n n n 的序列,满足 ∑ a i = 0 \sum a_i = 0 ai=0
可以对数列进行操作
对于索引 i , j ( 1 ≤ i , j ≤ n , i ≠ j ) i, j(1 \leq i, j \leq n, i \not= j) i,j(1i,jn,i=j),令 a [ i ] = a [ i ] + 1 , a [ j ] = a [ j ] − 1 a[i] = a[i]+1, a[j] = a[j]-1 a[i]=a[i]+1,a[j]=a[j]1
i < j i < j i<j,则该操作无需花费
否则,一次操作需要 1 1 1 点花费
问要使数组变成全为 0 0 0 的序列,至少需要多少花费

分析

对于所有 a [ j ] < 0 a[j] < 0 a[j]<0 的数,先用索引在 j j j 之前的数相抵
则剩余的数值中, ∑ a [ i ] ( a [ i ] > 0 ) \sum a[i] (a[i] > 0) a[i](a[i]>0) 就是最小的花费
当然求 ∑ a [ i ] ( a [ i ] < 0 ) \sum a[i](a[i]<0) a[i](a[i]<0) 也是可以的

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;;
const ll mod = 1e9 + 7;

int a[maxn];
queue<int> q;

int main() {
    int T;
    scanf("%d", &T);
    while(T--) {
        int n;
        scanf("%d", &n);
        while(!q.empty()) q.pop();
        for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
        for(int i = 1; i <= n; ++i) {
            while(a[i] < 0 && !q.empty()) {
                int top = q.front(); q.pop();
                if(a[i] + top < 0)  a[i] += top;
                else q.push(top + a[i]), a[i] = 0;
            }
            if(a[i] > 0) q.push(a[i]);
        }
        ll ans = 0;
        while(!q.empty()) {
            ans += 1ll * q.front(); q.pop();
        }
        printf("%lld\n", ans);
    }
    return 0;
}


C - Balanced Bitstring [思维]

题意

给出一个长度为 n n n,只包含字符 0,1,? 的字符串
字符 ? 可以转换成 0,1 中的任意一个字符
问改串是否能满足在任意长度为 k k k 的连续子串中,0,1 的个数相同

分析

由于任意长度为 k k k 的连续子串,都要满足个数相同,比如 k = 4 k=4 k=4
[1, 0, ?, 1], ?,下一个串就是 1, [0, ?, 1, ?],可以发现相距为 k k k 的字符必须要是一样的,才可以满足题目所要求的
除此之外,还需要判断第一个长度为 k k k 的串是否可以满足 0,1 个数相同
只要 0,1 其中任意一个的个数不会超过一半,或者说其他两个字符个数和即可

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3e5 + 5;;
const ll mod = 1e9 + 7;

char s[maxn];
int cnt[3];

int main() {
    int T;
    scanf("%d", &T);
    while(T--) {
        int n, k;
        scanf("%d%d%s", &n, &k, s + 1);
        for(int i = 1; i <= n; ++i) {
            if(s[i] == '?') s[i] = '2';
        }
        bool flag = true;
        for(int i = 1; i <= k; ++i) {
            cnt[0] = cnt[1] = cnt[2] = 0;
            for(int j = i; j <= n; j += k) cnt[s[j]-'0']++;
            if(cnt[0] && cnt[1]) {
                flag = false;
                break;
            }else if(cnt[0]) {
                for(int j = i; j <= n; j += k) s[j] = '0';
            }else if(cnt[1]) {
                for(int j = i; j <= n; j += k) s[j] = '1';
            }
        }
        cnt[0] = cnt[1] = cnt[2] = 0;
        for(int i = 1; i <= k; ++i) cnt[s[i]-'0']++;
        if(cnt[0] > k/2 || cnt[1] > k/2) flag = false;
        puts(flag ? "YES" : "NO");
    }
    return 0;
}


D - Tree Tag [博弈][dfs][树的直径] ★★

题意

给一棵树,Alice从 a a a 出发,每次移动最大可移动距离为 d a da da,Bob从 b b b 出发。每次移动的最大可移动距离为 d b db db
可移动距离即为路径上的边数
他们两个都可以跳无限步,且轮流跳,Alice先手。若 Alice 和 Bob 最终会跳到同一个点上,则为 Alice 赢,否则为 Bob 赢

分析

参考博客
d e p a b depab depab a , b a,b a,b 简单路径的距离; l e n len len 为树的直径; 比较容易想到的几个点是

  1. d e p a b ≤ d a depab \leq da depabda,显然这种情况下, A A A 可以直接跳到 B B B 的初始位置,所以是 Alice 赢
  2. d b > 2 × d a db > 2 \times da db>2×da,这种情况下,不管第一次 A A A 跳到哪, B B B 只要保持与 A A A 距离超过 d a da da 的点就可以不被抓到,所以是 Bob 赢

但是在 2 的情况下,有一个问题是 B B B 如果没办法跳到那么远的点,即没有那么远的点可以跳,那么 B B B 无法逃离 A A A 的魔爪
既然要求可以跳的距离,那么可以想到树的直径,直径长度即左右叶子结点的最大距离
如果这个最大距离 l e n len len 满足 l e n ≤ 2 × d a len \leq 2 \times da len2×da,那么 A A A 可以一次性跳到树的直径的中间,不管 B B B 跑到哪个地方, A A A 都可以一次抓到
当然这个判断要在 2 之前

除去这些还剩下 d b ≤ 2 × d a db \leq 2 \times da db2×da 这种情况,显然 A A A 也能与 B B B 在同一个点(具体可看参考博客,他写的很详细)

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
const ll mod = 1e9 + 7;

vector<int> ed[maxn];
int dep[maxn];
int depab;

void dfs(int u, int fa, int deep, int b) {
    if(u == b) {
        depab = deep;
        return ;
    }
    for(auto v : ed[u]) {
        if(v == fa) continue;
        dfs(v, u, deep+1, b);
    }
}

void dfs2(int u, int fa, int deep) {
    dep[u] = deep;
    for(auto v : ed[u]) {
        if(v == fa) continue;
        dfs2(v, u, deep+1);
    }
}

int main() {
    int T;
    scanf("%d", &T);
    while(T--) {
        int n, a, b, da, db;
        scanf("%d%d%d%d%d", &n, &a, &b, &da, &db);
        for(int i = 1; i <= n; ++i) ed[i].clear();
        for(int i = 1, u, v; i < n; ++i) {
            scanf("%d%d", &u, &v);
            ed[u].push_back(v), ed[v].push_back(u);
        }
        int rt;
        dfs(a, 0, 0, b);
        if(da >= depab) {
            puts("Alice");
        }else{
            rt = 1;
            dfs2(rt, rt, 0);
            for(int i = 1; i <= n; ++i) {
                if(dep[rt] < dep[i]) rt = i;
            }
            dfs2(rt, rt, 0);
            for(int i = 1; i <= n; ++i) {
                if(dep[rt] < dep[i]) rt = i;
            }
            // printf("in -- %d\n", dep[rt]);
            if(2 * da >= dep[rt])   puts("Alice");
            else if(db > 2 * da)    puts("Bob");
            else puts("Alice");
        }
    }
    return 0;
}


E - Fixed Point Removal [线段树] ★★★

题意

给一个长度为 n n n 的序列, m m m 次询问
每次询问给出 x , y x, y x,y,禁用掉该序列前 x x x 个数和最后 y y y 个数
对于每个 a [ i ] = = i a[i] == i a[i]==i 的位置,可以删去这个位置上的数,问数组最大可以删除数的次数

分析

参考博客
首先可以明确的是, a [ i ] = = i a[i] == i a[i]==i,就是可以删除的
a [ i ] > i a[i] > i a[i]>i,这个是不管如何都无法删除的
a [ i ] < i a[i] < i a[i]<i,这个需要令前面删除 a [ i ] − i a[i]-i a[i]i 个数才可以删去这个数
第一二个点都好解决,问题是第三个点(废话,一时没想到怎么解决
第三个点换个方式说,也就是对于其前面的 a [ i ] − i a[i]-i a[i]i 个可删去的数,都是无法给他们增加贡献的,但可以给更前面的数都增加 1 1 1 的贡献
是不是有点区间的感觉

由于值记录可以删除的数,那么这个删去的数组必然是非递减的
只要维护 a [ i ] a[i] a[i] 可以增加贡献的位置,然后求区间和,就可以得到结果

代码

#include <bits/stdc++.h>
#define lson id<<1
#define rson id<<1|1
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int maxn = 3e5 + 5;
const ll mod = 1e9 + 7;

vector<pair<int, int> > q[maxn];
int tree[maxn << 2];
int res[maxn];
int a[maxn];

void update(int id, int l, int r, int pos, int val) {
    if(l == pos && r == pos) {
        tree[id] += val;
        return ;
    }
    int mid = (l + r) >> 1;
    if(pos <= mid) update(lson, l, mid, pos, val);
    else    update(rson, mid+1, r, pos, val);
    tree[id] = tree[lson] + tree[rson];
}

int kth(int id, int l, int r, int k) { // 区间第 k 大
    if(l == r)  return l;
    int mid = (l + r) >> 1;
    if(tree[rson] >= k)
        return kth(rson, mid+1, r, k);
    else
        return kth(lson, l, mid, k-tree[rson]);
}

int query(int id, int l, int r, int ql, int qr) {
    if(ql <= l && qr >= r)  return tree[id];
    int mid = (l + r) >> 1, ans = 0;
    if(ql <= mid)   ans += query(lson, l, mid, ql, qr);
    if(qr > mid)    ans += query(rson, mid+1, r, ql, qr);
    return ans;
}

int main() {
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        a[i] = i >= a[i] ? i - a[i] : inf;
    }
    for(int i = 1, l, r; i <= m; ++i) {
        scanf("%d%d", &l, &r);
        q[n-r].push_back(make_pair(l + 1, i));
    }
    memset(tree, 0, sizeof(tree));
    for(int i = 1, ans = 0; i <= n; ++i) { // 包含开头禁用的序列长度
        if(a[i] == 0) {
            update(1, 1, n, i, 1);
            ans++;
        }else if(ans >= a[i]) {
            update(1, 1, n, kth(1, 1, n, a[i]), 1);
            ans++;
        }
        for(pair<int, int> j : q[i])
            res[j.second] = query(1, 1, n, j.first, n);
    }
    for(int i = 1; i <= m; ++i) printf("%d\n", res[i]);
    return 0;
}


补完撒花✿✿ヽ(°▽°)ノ✿

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值