「题解」Codeforces 755F PolandBall and Gifts

· · 题解

orz qyc

来我的cnblogs中获得应该会更好的体验

看成一个人 ip_i 连边,每个点的入度出度都为 1 。那么就是若干个环,每次可以选择一条边将这条边两端的端点染色,求 k 次染色后,最大和最小有颜色点的个数数。

最大值发现可以贪心,偶数长度的环可以用长度除以 2 次染色全部染色,每次染色的贡献都拉满了。奇数长度的环可以用长度减一除以 2 次染色将除了一个点其他全部染色,剩下的一个点最后还有染色机会再染。

最小值的话,每个环第一次开始染它色一定是贡献为 2,基于贪心先不开其他的环,再继续对这个环染色,每次贡献为 1,到最后一步贡献为 0。换而言之,开了一个环就将它染色到底。

发现对于一个长度为 a_i 的环,将其全部染色次数为 a_i,贡献为 a_i。那么一定想让若干个 a_i 组成 k,否则 k 多开的没染完色的那个环会多贡献 1

问题转化为给定若干个正整数 a_i,其和为 n,问能不能挑出一些 a_i 使得和为 k。也就是 01 背包可行性问题。

发现 bitset 和分治 NTT 都不大能过,注意到对 a_i 总和的限制,不同的 a_i 仅有 \mathcal{O}(\sqrt{n}) 个,把多个同样的物品合成一个物体有不同的次数,就变成了一个多重背包问题。

再加个二进制分组优化,时间复杂度大概是个 \mathcal{O}(n\sqrt n\log n/w),加上那个 \log 其实跑不大满,就冲过去了。

有一个有理有据的方法是类似根号分治,选择一个值域 T,小于等于 T 的数跑 \mathcal{O}(kT) 的多重背包可行性,每次枚举物品重量 i 之后再记录一个 g_j 代表用重量 \leq i 的物品,j 最少被几个 i 凑出来,就能 dp 了。>T 的用之前说到的 01 背包可行性问题,复杂度是 \mathcal{O}(\frac{nk}{wT}) 的,也就是 >T 的数不会超过 \frac{n}{T} 个,加上 bitset 优化即可。

后面那个做法的复杂度为 \mathcal{O}(kT+\frac{nk}{wT}),将 T 设为 100 发现计算次数可以接受,加上这个很难会被卡满,就过了。

代码是前面没有根号分治的做法。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<bitset>
typedef long long ll;
template <typename T> T Max(T x, T y) { return x > y ? x : y; }
template <typename T> T Min(T x, T y) { return x < y ? x : y; }
template <typename T> T Abs(T x) { return x < 0 ? -x : x; }
template <typename T> T chkmax(T &x, T y) { return x = x > y ? x : y; }
template <typename T>
T &read(T &r) {
    r = 0; bool w = 0; char ch = getchar();
    while(ch < '0' || ch > '9') w = ch == '-' ? 1 : 0, ch = getchar();
    while(ch >= '0' && ch <= '9') r = (r << 3) + (r <<1) + (ch ^ 48), ch = getchar();
    return r = w ? -r : r;
}
const int N = 1000010;
int n, p[N], a[N], m, k, ans1, ans2;
bool vis[N];
int ct[N], b[N], c[N], us[N];
std::bitset<1000001>f;
void dfs(int x, int s) {
    if(vis[x]) {
        a[++m] = s;
        return ;
    }
    vis[x] = 1;
    dfs(p[x], s+1);
}
signed main() {
    read(n); read(k); int _n = n;
    for(int i = 1; i <= n; ++i) read(p[i]);
    for(int i = 1; i <= n; ++i)
        if(!vis[i])
            dfs(i, 0);
    n = m;
    std::sort(a + 1, a + n + 1);
    int k_ = k;
    for(int i = 1; i <= n; ++i) {
        if(!k_) break ;
        int t = Min(a[i] / 2, k_);
        ans2 += t * 2; k_ -= t;
    }
    for(int i = 1; i <= n; ++i)
        if((a[i] & 1) && k_)
            ++ans2,
            --k_;
    //
    for(int i = 1; i <= _n; ++i) ct[a[i]]++;
    m = 0;
    for(int i = 1; i <= _n; ++i)
        if(ct[i])
            b[++m] = i,
            c[m] = ct[i];
    f[0] = 1;
    for(int i = 1; i <= m; ++i) {
        int tc = c[i];
        for(int j = 1; tc; j <<= 1) {
            int t = Min(j, tc);
            f |= f << (b[i] * t),
            tc -= t;
        }
    }
    if(f[k]) ans1 = k;
    else ans1 = k+1;
    printf("%d %d\n", ans1, ans2);
    return 0;
}