Codeforces Round #646 (Div. 2) A - E 解题报告

题目链接



\(A. Odd Selection\)

\(Description:\)
  给定长度为 \(n\) 的数列,选出 \(x\) 个数,问是否存在一种方案使得这 \(x\) 个数的和为奇数?
\(Solution:\)
  计算出数列中的奇数和偶数的个数,然后优先选择偶数,最后再选择奇数个奇数即可。
\(Code:\)

/*
@Author: nonameless
@Date:   2020-06-01 08:10:05
@Email:  2835391726@qq.com
@Blog:   https://www.cnblogs.com/nonameless/
*/
#include <bits/stdc++.h>
#define x first
#define y second
#define pb push_back
#define sz(x) (int)x.size()
#define all(x) x.begin(), x.end()
using namespace std;
typedef long long ll;
typedef pair<ll, ll> PLL;
typedef pair<int, int> PII;
const double eps = 1e-8;
const double PI  = acos(-1.0);
const int INF = 0x3f3f3f3f;
const ll LNF  = 0x3f3f3f3f3f3f;
inline int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }
inline ll  gcd(ll  a, ll  b) { return b ? gcd(b, a % b) : a; }
inline int lcm(int a, int b) { return a * b / gcd(a, b); }

const int N = 1e3 + 10;

int a[N];

int main(){

    int t; cin >> t;
    while(t --){
        int n, x; cin >> n >> x;
        int odd = 0, even = 0;
        for(int i = 1; i <= n; i ++){
            int num; cin >> num;
            if(num & 1) odd ++;
            else even ++;
        }
        int t = max(0, x - even); // 我们需要选择的奇数的个数
        if(even && t % 2 == 0) t ++; // 个数为偶数要变为奇数,条件是必须有偶数存在
        if(odd >= t && t & 1) puts("YES");
        else puts("NO");
    }
    return 0;
}



\(B. Subsequence Hate\)

\(Description:\)
  给定一个二进制字符串,满足任何一个 \(1\) 的两边不能同时有 \(0\),任何一个 \(0\) 两边不能同时有 \(1\) 就是好字符串,你可以修改原字符串(\(0 -> 1\ or\ 1 -> 0\))。问最少几次操作使得原字符串变为好字符串?
\(Solution:\)
  最后的字符串显然变成了:\(000011111\) 或者 \(111110000\) 这两种类似的格式,所以我们可以枚举中间变化的那个点,计算代价可以通过预处理出每个位置的左右有多少个 \(01\)
\(Code:\)

/*
@Author: nonameless
@Date:   2020-06-01 08:39:38
@Email:  2835391726@qq.com
@Blog:   https://www.cnblogs.com/nonameless/
*/
#include <bits/stdc++.h>
#define x first
#define y second
#define pb push_back
#define sz(x) (int)x.size()
#define all(x) x.begin(), x.end()
using namespace std;
typedef long long ll;
typedef pair<ll, ll> PLL;
typedef pair<int, int> PII;
const double eps = 1e-8;
const double PI  = acos(-1.0);
const int INF = 0x3f3f3f3f;
const ll LNF  = 0x3f3f3f3f3f3f;
inline int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }
inline ll  gcd(ll  a, ll  b) { return b ? gcd(b, a % b) : a; }
inline int lcm(int a, int b) { return a * b / gcd(a, b); }

const int N = 1e3 + 10;
int f1[N], f2[N], f3[N], f4[N];
char s[N];

int main(){

    int t; cin >> t;
    while(t --){
        scanf("%s", s + 1);
        int len = strlen(s + 1);
        memset(f1, 0, sizeof f1); // f1[i]: [1, i] 有多少个 0
        memset(f2, 0, sizeof f2); // f2[i]: [i, n] 有多少个 1
        memset(f3, 0, sizeof f3); // f3[i]: [1, i] 有多少个 1
        memset(f4, 0, sizeof f4); // f4[i]: [i, n] 有多少个 0

        for(int i = 1; i <= len; i ++){
            f1[i] = f1[i - 1] + (s[i] == '0');
            f3[i] = f3[i - 1] + (s[i] == '1');
        }

        for(int i = len; i >= 1; i --){
            f2[i] = f2[i + 1] + (s[i] == '1');
            f4[i] = f4[i + 1] + (s[i] == '0');
        }

        int ans = INF;
        for(int i = 1; i <= len; i ++){
            int t1 = f1[i] + f2[i + 1];  // 1111100000 形式
            int t2 = f3[i] + f4[i + 1];  // 0000011111 形式
            ans = min({ans, t1, t2});
        }

        cout << ans << endl;
    }
    return 0;
}


\(C. Game On Leaves\)

\(Descrition:\)
  给定一棵树和一个结点 \(x\),两人每次轮流删除一个度为 \(1\) 的点(所在边随之删除),谁先删除 \(x\) 谁赢,两人都采取最优策略,问谁赢?
\(Solution:\)
  首先可以想到的是如果 \(x\) 的度为 \(1\),那么先手赢。排除掉这种情况之后,又由于两人都采取的最优的策略,那么显然 \(x\) 会是最后删除的一个点,而由于是树,总共有 \(n - 1\) 条边,所以只需要 \(n - 1\) 步就可以把 \(x\) 删去,显然当 \(n - 1\) 是奇数时先手必赢,偶数就后手赢。
\(Code:\)

/*
@Author: nonameless
@Date:   2020-06-01 09:04:43
@Email:  2835391726@qq.com
@Blog:   https://www.cnblogs.com/nonameless/
*/
#include <bits/stdc++.h>
#define x first
#define y second
#define pb push_back
#define sz(x) (int)x.size()
#define all(x) x.begin(), x.end()
using namespace std;
typedef long long ll;
typedef pair<ll, ll> PLL;
typedef pair<int, int> PII;
const double eps = 1e-8;
const double PI  = acos(-1.0);
const int INF = 0x3f3f3f3f;
const ll LNF  = 0x3f3f3f3f3f3f;
inline int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }
inline ll  gcd(ll  a, ll  b) { return b ? gcd(b, a % b) : a; }
inline int lcm(int a, int b) { return a * b / gcd(a, b); }


int main(){

    int t; cin >> t;
    while(t --){
        int n, x;
        cin >> n >> x;
        int cnt = 0; // 计算 x 的度
        for(int i = 1; i < n; i ++){
            int u, v; cin >> u >> v;
            if(u == x || v == x) cnt ++;
        }
        if(cnt <= 1 || n % 2 == 0) puts("Ayush");
        else puts("Ashish");
    }
    return 0;
}



\(D. Guess The Maximums\)

\(Description:\)
  交互式题,看了半天才看懂。
  给定 \(n, k\),意为有一个长度为 \(n\) 的数组 \(A\)(未知,但值域在 \([1,\ n]\) 中),让你解密出长度为 \(k\) 的密码序列 \(P\)。再给定 \(k\) 个集合(集合两两不相交),集合里的元素是 \(A\) 的下标,对于一个集合 \(S\) 可以解出一个密码,即:

\[P_i = \max_{j \notin S_i}A[j] \]

  你可以进行 \(12\) 次询问,每次询问你也将提供一个集合 \(T\)(和 \(S\) 一样也是代表下标),程序会返回 \(\max_{j \in T}A[j]\)。最后你需要解出密码序列 \(P\)
\(Solution:\)
  由于 \(S\) 解出密码是他补集的最大值 \(mx\),那么分两种情况:

  1. 最大值在其中一个 \(S\)
    其他 \(k - 1\) 个集合解出密码就是 \(mx\)
  2. 最大值不在 \(S\)
    \(k\) 个集合解出的密码都是 \(mx\)

  所以最少有 \(k - 1\) 个密码是 \(mx\),所以我们可以先用一次查询得出最大值 \(mx\),然后来判断是否存在一个集合中包含了 \(mx\) 得下标?由于集合得个数有 \(1000\) 个,而我们只剩 \(11\) 次查询了,而这两个数字,显然是在提醒我们用二分。
  我们可以二分来确定 \(mx\) 是否存在在某个集合。给每个集合一个下标,确定了 \(l,\ r\),然后算出 \(mid\),将 \([l,\ mid]\) 得集合取并集,作为查询集合,如果返回的是 \(mx\),那么说明 \(mx\)\([l,\ mid]\),否则就在 \([mid + 1,\ r]\) 中,最后根据确定的那个集合,再查询一次他的补集,即得到他确定的密码(因为我们已经确定了其他 \(k - 1\) 个集合对应的密码就是 \(mx\) 了)。
\(Code:\)

/*
@Author: nonameless
@Date:   2020-06-01 11:32:21
@Email:  2835391726@qq.com
@Blog:   https://www.cnblogs.com/nonameless/
*/
#include <bits/stdc++.h>
#define x first
#define y second
#define pb push_back
#define sz(x) (int)x.size()
#define all(x) x.begin(), x.end()
using namespace std;
typedef long long ll;
typedef pair<ll, ll> PLL;
typedef pair<int, int> PII;
const double eps = 1e-8;
const double PI  = acos(-1.0);
const int INF = 0x3f3f3f3f;
const ll LNF  = 0x3f3f3f3f3f3f;
inline int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }
inline ll  gcd(ll  a, ll  b) { return b ? gcd(b, a % b) : a; }
inline int lcm(int a, int b) { return a * b / gcd(a, b); }

const int N = 1e3 + 10;

int n, k;
vector<int> s[N];

int ans[N];

// 查询
int query(vector<int> ask){
    printf("? %d", sz(ask));
    for(int i = 0; i < sz(ask); i ++)
        printf(" %d", ask[i]);
    puts("");
    fflush(stdout); // 用 printf 一定要写
    int res; scanf("%d", &res);
    return res;
}

int main(){

    int t; cin >> t;
    while(t --){
        cin >> n >> k;
        for(int i = 1; i <= k; i ++){
            int c; cin >> c;
            s[i].clear();
            for(int j = 1; j <= c; j ++){
                int x; scanf("%d", &x);
                s[i].pb(x);
            }
        }

        vector<int> ask; // 查询的集合
        for(int i = 1; i <= n; i ++) ask.pb(i);
        int mx = query(ask); // 得到最大值
        int l = 1, r = k, mid;
        while(l < r){
            mid = l + r >> 1;
            ask.clear();
            // 这里取 [l, mid] 的并集
            for(int i = l; i <= mid; i ++)
                for(int j = 0; j < sz(s[i]); j ++)
                    ask.pb(s[i][j]);
            int x = query(ask);
            if(x == -1) return 0;
            if(x == mx) r = mid;
            else l = mid + 1;
        }  
        ask.clear();
        // 得到他的补集
        map<int, int> mp;
        for(int i = 0; i < sz(s[l]); i ++) mp[s[l][i]] = 1;
        for(int i = 1; i <= n; i ++) if(!mp[i]) ask.pb(i);
        int x = query(ask);
        if(x == -1) return 0;
        printf("!");
        for(int i = 1; i <= k; i ++){
            if(i == l) printf(" %d", x);
            else printf(" %d", mx);
        }
        puts("");
        fflush(stdout);
        string ss; cin >> ss;
        if(ss == "Incorrect") return 0;
    }
    return 0;
}



\(E. Tree Shuffling\)

\(Description:\)
  给定一颗树,每个结点都有一个初始值 \(b_i\) 和目标值 \(c_i\) 和一个代价 \(a_i\),要求最后每个结点的值变为 \(c_i\),对于以 \(u\) 为根的子树,他要改变他的子树中的 \(t\) 个点的值(将他们的 \(b_i\) 以某种顺序排列)的代价为 \(t * a_u\),问最小代价是多少?其中特别的是:\(0 \leq b_i,c_i \leq 1\)
\(Solution:\)
  读题的时候没有注意 \(b_i,c_i\) 的限制,发现做不了,看了官方题解才发现。。。
  就是一个树上贪心,记录一下每颗子树的 \(b_i\) 值 的 \(0\)\(1\) 的个数,把父节点的 \(a_i\) 往下传,和当前结点取 \(min\) 得到改变的最小代价。最后判断一下是否还有没有多的 \(0\)\(1\) 存在即可。感觉比 \(D\) 要简单。
\(Code:\)

/*
@Author: nonameless
@Date:   2020-06-01 10:38:05
@Email:  2835391726@qq.com
@Blog:   https://www.cnblogs.com/nonameless/
*/
#include <bits/stdc++.h>
#define x first
#define y second
#define pb push_back
#define sz(x) (int)x.size()
#define all(x) x.begin(), x.end()
using namespace std;
typedef long long ll;
typedef pair<ll, ll> PLL;
typedef pair<int, int> PII;
const double eps = 1e-8;
const double PI  = acos(-1.0);
const int INF = 0x3f3f3f3f;
const ll LNF  = 0x3f3f3f3f3f3f;
inline int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }
inline ll  gcd(ll  a, ll  b) { return b ? gcd(b, a % b) : a; }
inline int lcm(int a, int b) { return a * b / gcd(a, b); }

const int N = 2e5 + 10, M = N << 1;

int n;
int a[N], b[N], c[N];
ll ans = 0;

int h[N], to[M], nxt[M], idx = 0;

void add(int u, int v){
    to[ ++ idx] = v; nxt[idx] = h[u]; h[u] = idx;
}

PII dfs(int cur, int fa, int w){ // 当前结点,父节点,父节点传下来的 w
    w = min(w, a[cur]);
    PII res = {0, 0};
    if(b[cur] != c[cur]){
        if(b[cur] == 1) res.x ++;
        else res.y ++;
    }
    for(int i = h[cur]; i; i = nxt[i]){
        int v = to[i];
        if(v == fa) continue;
        PII tmp = dfs(v, cur, w);
        res.x += tmp.x; res.y += tmp.y; // 加上子树的
    }
    int cnt = min(res.x, res.y); // 直接处理
    ans += 2ll * cnt * w;
    res.x -= cnt; res.y -= cnt;
    return res;
}



int main(){
     
    cin >> n;
    for(int i = 1; i <= n; i ++)
        scanf("%d%d%d", &a[i], &b[i], &c[i]);
    
    for(int i = 1, u, v; i < n; i ++){
        scanf("%d%d", &u, &v);
        add(u, v); add(v, u);
    }

    PII res = dfs(1, -1, INF);
    if(res.x || res.y) puts("-1");
    else cout << ans << endl;
    
    return 0;
}

posted @ 2020-06-01 17:28  nonameless  阅读(76)  评论(0编辑  收藏  举报