Codeforces Round 936 (Div. 2)

基本情况

\(C\) 30min时候就想出很接近正解的做法了,但是没想清楚就草草否决后面改了一个很麻烦的做法,一小时才过。

\(D\) 原题,不屑于抄。

C. Tree Cutting

Problem - C - Codeforces

二分答案肯定不用说。

有一个直觉,要维护每个点的子树大小,然后把所有子树大小合适的点全部剪掉。

但直接比较子树大小和目标值是不可行的,因为删掉一个子树的子树会影响到其子树的大小。

其实要修改很简单,如果该子树合适直接剪掉,然后返回值改成0即可。

void solve() {
    int n, k;
    std::cin >> n >> k;
    std::vector adj(n, std::vector<int>());
    std::vector<int> dp(n, 1);
    for (int i = 0, u, v; i < n - 1; ++i) {
        std::cin >> u >> v;
        --u, --v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }

    int lo(1), hi(n);

    while(lo <= hi) {
        int mid(lo + hi >> 1);
        int cnt(0);
        auto dfs = [&](auto self, int x, int fa) -> int {
            int siz(1);
            for (auto& to : adj[x]) if (to != fa) {
                siz += self(self, to, x);
            }
            if (siz >= mid) {//符合条件直接剪掉就好了
                cnt++;
                return 0;//剪掉后这个子树大小就是0
            }
            return siz;
        };
        dfs(dfs, 0, -1);
        if (cnt > k) {
            lo = mid + 1;
        }
        else {
            hi = mid - 1;
        }
    }

    std::cout << lo - 1 << '\n';

}   

然而当时不细想就草草否决,改成了自觉的“稳妥”的离线做法,多浪费了半小时。

void solve() {
    int n, k;
    std::cin >> n >> k;
    std::vector adj(n, std::vector<int>());
    std::vector<int> t, f(n), pos(n);
    for (int i = 0, u, v; i < n - 1; ++i) {
        std::cin >> u >> v;
        --u, --v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    auto dfs = [&](auto self, int x, int fa) -> void {
        t.push_back(x);
        pos[x] = sz(t);
        for (auto& to : adj[x]) if (to != fa) {
            self(self, to, x);
        }
        return ;
    };

    dfs(dfs, 0, -1);
    int lo(0), hi(n);

    auto check = [&](auto& mid) -> bool {
        int cnt = 0;
        for (int i = n - 1; i >= 0; i--) {
            int u = t[i];
            f[u] = 1;
            for (auto to : adj[u]) if (pos[to] >= pos[u]) {
                f[u] += f[to];
            }
            if (f[u] >= mid) {
                f[u] = 0;//典型的脱裤子放屁
                cnt++;
            }
        }
        return cnt > k;
    };

    while (lo <= hi) {
        int mid(lo + hi >> 1);
        if (check(mid)) {
            lo = mid + 1;
        }
        else {
            hi = mid - 1;
        }
    }
    std::cout << lo - 1 << '\n';
}

D. Birthday Gift

Problem - D - Codeforces

算复习一下原题吧。

H

https://ac.nowcoder.com/acm/contest/67741/H

位运算好题

  • 先考虑每个物品的重量都只含 \(1bit\)​ 的情况,可以帮助对这题要解决什么问题有个大致了解
  • 记所选物品重量或起来是 \(𝑐\)枚举 𝑚 里是 \(1\) 的某个 \(bit\),强制 𝑐 里该位为 \(0\),则该位将 𝑚 分成了前后两部分
    • 对于前面的那部分位(更高位),要求所选的物品这些位必须是 𝑚 的子集(即 𝑚 对应位是 \(1\) 才能选)
    • 对于后面的那部分位(更低位),没有任何限制
  • 因此,枚举 𝑚 里每一位作为这个分界,每个物品就变成了要么能选要么不能选、彼此之间也不影响,所以把能选的都选上就好,最后再特判一下 \(c=m\) 的状况,即可保证枚举了所有情况
void solve() {
    int n, m;
    cin >> n >> m;
    vector<int> v(n), w(n);
    for (int i = 0; i < n; i++) cin >> v[i] >> w[i];
    ll ans = 0;
    auto get = [&](int s) {
        ll res = 0;
        for (int i = 0; i < n; i++) {
            if ((s & w[i]) == w[i]) {
                res += v[i];
            }
        }
        ans = max(ans, res);
    };
    for (int i = 29; i >= 0; i--) {
        if (m >> i & 1) {
            get((m ^ (1 << i)) | ((1 << i) - 1));//很关键
        }
    }
    get(m);//处理C=M的情况
    cout << ans << endl;
}

get函数内的传参用了几个 trick.

  • m ^ (1 << i)//把 m 的第 i 位取反,这里是置为 0
    
  • (m ^ (1 << i) | ((1 << i) - 1))//把 i 位之前(右边)的位数全部变成 1,因为由题解,更低位不限制
    //1 << i 是第 i 位为 1,右边都是 0
    //1 << i - 1 则会把第 i 位变成 0, 右边全部变成 1
    

通过这个参数与 \(w_i\) 进行按位与,显然能自动检测符合题解思路条件的物品,全部加上即可。

本题

那就没啥好说了,几乎是一致的,只是 \(get\) 的时候改成题目要求的连续异或和,符合条件的异或和加入背包中,更新最大值就行了。

void solve() {
    int n, m;
    std::cin >> n >> m;
    std::vector<int> a(n);
    for (auto& x : a) std::cin >> x;
    int xorSum(0);
    for (auto& x : a) xorSum ^= x;
    if (xorSum > m) {error ; return ;}
    i64 ans(0);
    auto get = [&](int mask) {
        i64 res(0), cnt(0);
        for (auto& x : a) {
            res ^= x;
            if ((res & mask) == res) {
                cnt++;
                res = 0;
            }
        }
        if (res != 0) {//因为他是要求连续的整段都要合法,如果剩下的最后一段不能并入mask,那就不合法
            cnt = 0;
        }
        ans = std::max(ans, cnt);
    };

    for (int i = 0; i < 31; i++) {
        if (m >> i & 1) {
            get((m ^ (1 << i)) | ((1 << i) - 1));
        }
    }
    get(m);
    std::cout << ans << '\n';
}

E. Girl Permutation

Problem - E - Codeforces

看起来就很组合数,而且有一组大样例,直接对着找规律。

input:
20 5 4
1 2 3 4 12
12 13 18 20

output:
317580808

首先毋庸置疑最大值在 \(12\) 这个位置

其次,\([5, 11]\) 这个区间的数字一定不能大于 \(a_4\),因为如果大于就也会被算成前缀最大。

同理,\([14, 17],[19,19]\) 这几个区间的数字一定不能大于 \(a_{18}, a_{20}\)

\(12\) 的位置上是 \(20\) 已经确定了,分析剩下的 \(19\)​ 个数字。

  • 先分析左边
    • 左边先给 \(11\) 个数字,\(\binom{19}{11}\)
    • 然后 \(a_4\) 上的数字肯定是 \(11\) 个数字里面最大的,现在剩 \(10\) 个数字。
    • 在剩下的 \(10\) 个数字中选 \(7\) 个排入 \([5, 11]\),任意顺序,\(A_{10}^{7}\)
    • 然后还剩下三个数字,按顺序放入,只有一种方案。
  • 再分析右边
    • 同理 \(A_{6}^{4}\)

\[C_{19}^{11}A_{10}^7A_{6}^{4} = 317580808 \]

然后代码实现就好了。

void solve() {
    i64 x, y;
    int n;
    std::cin >> n >> x >> y;
    std::vector<int> a(x + 1), b(y + 1);
    for (int i = 1; i <= x; i++) std::cin >> a[i];
    for (int i = 1; i <= y; i++) std::cin >> b[i];
    if (a[x] != b[1] or a[1] != 1 or b[y] != n) {std::cout << 0 << '\n'; return ;}
    int remain(a[x] - 1);//先把中间最大的确定下来,减一
    Z ans(1);
    int last(a[x]);
    for (int i = x - 1; i >= 1; i--) {//左边先来一遍
        remain -= 1;//先把最大的确定下来,减一
        int num(last - a[i] - 1);//中间有几个自由数字
        ans *= comb.A(remain, num);
        remain -= num;//减去这些自由数字
        last = a[i];
    }
    remain = n - a[x];
    last = a[x];
    for (int i = 2; i <= y; i++) {//右边再来一遍
        remain -= 1;
        int num(b[i] - last - 1);
        ans *= comb.A(remain, num);
        remain -= num;
        last = b[i];
    }
    ans *= comb.binom(n - 1, a[x] - 1);
    std::cout << ans << '\n';
}
posted @ 2024-03-23 16:20  加固文明幻景  阅读(8)  评论(0编辑  收藏  举报