若 \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]];
时间复杂度 $O(n \times m)$。肯定会爆,考虑优化。
我们可以知道大多数的环中的人的数量是相同的。那么这题就转化成了多重背包,我们就可以用二进制优化。
```cpp
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
~~我代码写得太差了,建议大家参考其他题解的代码。~~
```cpp
#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;
}
```
## 后记
~~本蒟蒻写过的最长的一篇题解吧。~~
在本人退役前或有空会来解答问题。