Codeforces Round #646 (Div. 2)

康复场1。

https://codeforces.com/contest/1363

A - Odd Selection

题意:问是否能在给出的 (n) 个数中选恰好 (x) 个数,使得他们的和为奇数。

题解:必须选择奇数个奇数,然后不用思考这么复杂,枚举选择 ([1,x]) 个奇数是否可行,即可。

int n, x;

void TestCase() {
    scanf("%d%d", &n, &x);
    int odd = 0, even = 0;
    for(int i = 1; i <= n; ++i) {
        int ai;
        scanf("%d", &ai);
        if(ai % 2 == 1) {
            ++odd;
        } else {
            ++even;
        }
    }
    bool suc = 0;
    for(int i = 1; i <= x; i += 2) {
        if(i > odd)
            break;
        if(x - i > even)
            continue;
        suc = 1;
        break;
    }
    if(suc) {
        puts("Yes");
    } else {
        puts("No");
    }
    return;
}

想预处理之后 (O(1)) 来做,就要先使用(不超过x的)奇数个奇数,然后尽可能使用偶数。最后还要验证这些和确实是奇数才行(有可能使用了)

B - Subsequence Hate

题意:给一个01串,每次操作可以翻转一个位置,求至少多少次操作才能使得给出的01串中不含子序列"010"也不含子序列"101"。

题解:构造出的串必须是先0后1或者先1后0,搞个前缀和和后缀和统计就行。

int n;
char s[1005];

void TestCase() {
    scanf("%s", s + 1);
    n = strlen(s + 1);
    int sum0 = 0;
    int sum1 = 0;
    for(int i = 1; i <= n; ++i) {
        sum0 += (s[i] == '0');
        sum1 += (s[i] == '1');
    }
    int ans = sum0;
    int cnt0 = 0;
    int cnt1 = 0;
    for(int i = 1; i <= n; ++i) {
        sum0 -= (s[i] == '0');
        sum1 -= (s[i] == '1');
        cnt0 += (s[i] == '0');
        cnt1 += (s[i] == '1');
        ans = min(ans, cnt0 + sum1);
        ans = min(ans, cnt1 + sum0);
    }
    printf("%d
", ans);
    return;
}

C - Game On Leaves

题意:给出一棵 (n) 个点的无根树和一个节点 (x) ,两个人玩游戏,轮流操作。每次操作可以选择一个叶子(度数不超过1的节点)去除,谁去除了节点 (x) 谁就获胜,问最优策略下谁赢。

题解:若 (x) 是叶子,直接去除,先手赢,否则必须要经过一个状态(因为最优策略下没有人会先把 (x) 变成叶子,所以总是保留至少度数为2),就是 text P - X - P ,这个状态是后手必胜,然后求出初始状态里这个状态相差的节点个数的奇偶性就可以了。

D - Guess The Maximums

题意:交互题,题意太复杂见原题。

提示:注意 (S_i) 的并集未必是 ([1,n])

题解:看到这个12,大概都会往折半去想,有一个办法就是先用1次确定全集的最大值是多少,然后用至多10次来确定这个最大值在哪里(最坏情况下每次询问的长度是500-250-125-63-32-16-8-4-2-1),然后知道最大值的位置之后,找出最大值所在的 (S_i) (假如有的话),然后把其他的合并再询问最后一次。

int n, k;
int color[1005];
 
int maxnum, maxidx;
 
int query(int L, int R) {
    printf("? %d", R - L + 1);
    for(int i = L; i <= R; ++i) {
        printf(" %d", i);
    }
    printf("
");
    fflush(stdout);
    int x;
    scanf("%d", &x);
    assert(1 <= x && x <= n);
    return x;
}
 
bool check(int L, int R) {
    return query(L, R) == maxnum;
}
 
vector<int> tmp;
 
void TestCase() {
    scanf("%d%d", &n, &k);
    memset(color, -1, sizeof(color));
    for(int i = 1; i <= k; ++i) {
        int s;
        scanf("%d", &s);
        for(int j = 1; j <= s; ++j) {
            int x;
            scanf("%d", &x);
            color[x] = i;
        }
    }
    maxnum = query(1, n);
    int L = 1, R = n, M;
    while(1) {
        M = (L + R) / 2;
        if(L == M) {
            if(check(L, M)) {
                maxidx = L;
            } else {
                maxidx = R;
            }
            break;
        }
        if(check(L, M)) {
            R = M;
        } else {
            L = M + 1;
        }
    }
    tmp.clear();
    for(int i = 1; i <= n; ++i) {
        if(color[i] != color[maxidx]) {
            tmp.push_back(i);
        }
    }
    if(color[maxidx] == -1) {
        printf("!");
        for(int i = 1; i <= k; ++i) {
            printf(" %d", maxnum);
        }
        printf("
");
        fflush(stdout);
        char s[20];
        scanf("%s", s + 1);
        assert(s[1] == 'C');
        return;
    }
    printf("? %d", (int)tmp.size());
    for(auto &v : tmp) {
        printf(" %d", v);
    }
    printf("
");
    fflush(stdout);
    int x;
    scanf("%d", &x);
    assert(1 <= x && x <= n);
 
    printf("!");
    for(int i = 1; i < color[maxidx]; ++i) {
        printf(" %d", maxnum);
    }
    printf(" %d", x);
    for(int i = color[maxidx] + 1; i <= k; ++i) {
        printf(" %d", maxnum);
    }
    printf("
");
    fflush(stdout);
    char s[20];
    scanf("%s", s + 1);
    assert(s[1] == 'C');
    return;
}

*E - Tree Shuffling

题意:给出一棵 (n) 个点的有根树,根是1号点。每个节点有三个值 (a,b,c)(a) 表示这个节点的操作代价, (b) 表示节点的初始状态(只能是0或1), (c) 表示节点的目标状态(只能是0或1)。每次操作可以选择一个节点,然后选择其子树中的任意个节点,然后把这些节点的值交换到你想要的样子,求把整棵树变成目标状态的最小代价。

题解:一开始想了一个树dp,是错的,这个树dp是 (dp[u]) 表示节点 (u) 的子树复原的最小代价,然后若不能复原则为无穷。若 (u) 可以复原,则 (dp[u]=sumlimits_{vin son(u)}min(dp[v],a[u]*dif[v])) ,其中 (dif[v]) 表示节点 (v) 中初始状态和目标状态不同的数量。后来打了一个 (a[u]=min(a[u],a[p])) 的补丁,还是错的。错在一棵树就算没办法复原也可以尽可能复原(利用子树中的低代价来复原大部分,只留下一种不能复原的)。

int n, x;
int a[200005];
int b[200005];
int c[200005];
vector<int> G[200005];

int siz[200005];
int cnt01[200005];
int cnt10[200005];

ll res;

void dfs(int u, int p, int ap) {
    a[u] = min(a[u], ap);
    siz[u] = 1;
    cnt01[u] = (b[u] == 0 && c[u] == 1);
    cnt10[u] = (b[u] == 1 && c[u] == 0);
    for(auto &v : G[u]) {
        if(v == p)
            continue;
        dfs(v, u, a[u]);
        siz[u] += siz[v];
        cnt01[u] += cnt01[v];
        cnt10[u] += cnt10[v];
    }
    int tmp = min(cnt01[u], cnt10[u]);
    cnt01[u] -= tmp;
    cnt10[u] -= tmp;
    res += 2ll * tmp * a[u];
    return;
}

void TestCase() {
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) {
        G[i].clear();
        scanf("%d%d%d", &a[i], &b[i], &c[i]);
    }
    for(int i = 1; i <= n - 1; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    res = 0;
    dfs(1, 0, INF);
    if(cnt01[1] || cnt10[1])
        res = -1ll;
    printf("%lld
", res);
    return;
}
原文地址:https://www.cnblogs.com/KisekiPurin2019/p/13041042.html