CF755F

前言

随机跳题跳来的。本来以为很简单,结果花了我这个蒟蒻三个多小时。果然还是太蒻了呀。还有题目中的拖拉机是什么呀?

于是,题解记之。

题意

传送门

分析

我们容易发现,若把每个人当做一个点,那么给人送礼物,则是向这个人连一条边。

看不懂?直接上图!

图片走丢力

上图表示的是:

5 2
3 4 1 5 2

这一组数据。当然有向或无向都对这题没有影响。

根据观察,容易得出,这个图是由若干个环组成的,并且,环的个数也就是连通块的个数。

那么,我们便可以处理出环的个数,与他所包含的点的个数,也就是人的个数。

inline int dfs(int x) {//处理出环的中有多少个点。
    v[x] = 1;
    if (v[p[x]]) return 1;
    return dfs(p[x]) + 1;
}
for (int i = 1; i <= n; i++)//处理有多少个环(连通块)
        if (!v[i]) cnt[++len] = dfs(i);

最大值

最大值相对于最小值会简单一点。

我们想一想很容易得出,若是有一个人没有礼物,那么他要给的对象与他自己都没有礼物。为避免思维僵化,请读者自行证明。

那么,我们便可以分奇数环与偶数换来讨论。设环中点的数量为 \(x\)

  1. 偶数个。那么就可以分成 \(\frac{k}{2}\) 组,使每一组任意一个人没带,则他要给的对象与他自己都没有礼物。
  2. 奇数个。那么就可以分成 \(\lfloor \frac{k}{2} \rfloor\) 组与多出来的一个人。

\(\sum \lfloor \frac{c[i]}{2} \rfloor \ge k\),那么,答案即为 \(k \times 2\)。否则我们就将 \(\sum \lfloor \frac{c[i]}{2} \rfloor \ge k\) 加上,然后再在奇数环中找多出来的一个数,直至加到大于 \(k\) 或 人数等于 \(n\) 时停止。

最小值

刚开始,我也觉得这是贪心,直至我 WA 了不知道多少次,想通了,这是个 DP !!!

在这里,给组对于贪心的 hack。

输入:

10 7
9 6 2 8 1 3 10 4 5 7

输出:

7 10

如下图。

图片走掉力

容易的,我们可以想出,尽量使一整个环内的人不带礼物。

略微证明一下:

设有一个环内都是没带礼物的人,人数为 \(x\),另一个环都带了礼物,剩下的环保持不变,且人数大于一。那么这两个环内一共就有 \(x\) 人没有礼物,但是当这个环有了一个人带了礼物,因为总共没带礼物的人数相同,那么两个环分别就有 \(x - 1\)\(1\) 个人没带礼物,根据最大值那里的推论,总共就有 \(x + 2\) 个人没有礼物。

那么,也就是说,我们需要使这 \(len\) 个环中,抽出几个环,若他们人数总值等于 \(k\),那么肯定是最少有 \(k\) 个人没有礼物,若不等于有 \(k + 1\) 人。请读者自行思考为什么。但是我还是会说的

根据上述内容可以得出若他们人数总值等于 \(k\),那么肯定是最少有 \(k\) 个人没有礼物。

若要使有 \(k + 1\) 个人没有礼物,则使没有礼物的人都不得到礼物。但是,我们肯定会多出来一个人不能给没有礼物的人礼物,则有一个带了礼物的人没有送来的礼物。

有点绕,自行理解一下,当然,看不懂,是我的问题。

显然就考虑背包 DP。

for (int i = 1; i <= len; i++)
        for (int j = k; j >= cnt[i]; j--)
            f[j] |= f[j - cnt[i]];

\(f[i]\) 表示能不能用若干个环组合出 \(i\) 个人没带礼物。

时间复杂度 \(O(n \times m)\)。肯定会爆,考虑优化。

我们可以知道大多数的环中的人的数量是相同的。那么这题就转化成了多重背包,我们就可以用二进制优化。

inline void fen(int x, int v) {二进制优化
    for (int i = 1; x > 0; i <<= 1) {
        int j = min(i, x);
        a[++l] = j * v;
        x -= j;
    }
}

时间复杂度就降下来了。可以过。

Code

我代码写得太差了,建议大家参考其他题解的代码。

#include <bits/stdc++.h>
using namespace std;
 
const int N = 1e6 + 5;
int n, p[N], cnt[N], len, k, ans;
int c[N], l, a[N], mx, f[N * 10];//f 开 1e7 是因为二进制拆分会产生 nlogn 个数。但到不了极限。
bool v[N];
 
inline int dfs(int x) {//处理出环的中有多少个点。
    v[x] = 1;
    if (v[p[x]]) return 1;
    return dfs(p[x]) + 1;
}

inline void fen(int x, int v) {二进制优化
    for (int i = 1; x > 0; i <<= 1) {
        int j = min(i, x);
        a[++l] = j * v;
        x -= j;
    }
}
 
int main() {
    ios::sync_with_stdio(0);
    cin >> n >> k;
    for (int i = 1; i <= n; i++) cin >> p[i];
    for (int i = 1; i <= n; i++)//处理有多少个环(连通块)
        if (!v[i]) cnt[++len] = dfs(i), c[cnt[len]]++;
    sort(cnt + 1, cnt + 1 + len);//最大值需要
    for (int i = 1; i <= n; i++) if (c[i]) fen(c[i], i);
    f[0] = 1;
    for (int i = 1; i <= l; i++)
        for (int j = k; j >= a[i]; j--)
            f[j] |= f[j - a[i]];
    int kk = k;
    if (!f[k]) cout << k + 1 << ' ';
    else cout << k << ' ';//最小值
    ans = 0;
    for (int i = len; i > 0; i--) {
        if (cnt[i] <= kk * 2) {
            if (cnt[i] & 1) ans += cnt[i] - 1, kk -= cnt[i] / 2, cnt[i] = 1;
            else ans += cnt[i], kk -= cnt[i] / 2, cnt[i] = 0;
        }
        else {//排序的原因,这样就可以提前 break。
            ans += kk * 2;
            kk = 0;
            break;
        }
        if (!kk) break;
    }
    for (int i = 1; i <= len; i++) {//处理奇数环
        if (!kk) {
            break;
        }
        else {
            kk -= cnt[i];
            ans += cnt[i];
        }
    }
    cout << min(ans, n);
    return 0;
}

后记

本蒟蒻写过的最长的一篇题解吧。

在本人退役前或有空会来解答问题。

posted @ 2023-11-04 23:11  lucky_cloud  阅读(3)  评论(0编辑  收藏  举报