P1543 [POI 2004] SZP
题目背景
一班班花 \(\color{red}{\texttt{y}}\color{black}{\texttt{hb}}\) 十分可爱。
题目描述
班花 \(\color{red}{\texttt{y}}\color{black}{\texttt{hb}}\) 作为某日的值日班长,在自习课上管理着 \(n\) 名同学。除了她以外每一名同学都监视着另一名同学。现在班花 \(\color{red}{\texttt{y}}\color{black}{\texttt{hb}}\) 需要选择尽量多的同学去搬卷子和答题卡,且使得对于这些同学中的每一名同学,至少有一位监视她的同学没有被选中。问班花 \(\color{red}{\texttt{y}}\color{black}{\texttt{hb}}\) 最多可以选择多少同学。
由于班花 \(\color{red}{\texttt{y}}\color{black}{\texttt{hb}}\) 太可爱了,所以没有人监视她,也可以认为她的学号是 \(0\)。
如果一个人没有被监视,那么她就不能被选择。
输入格式
第一行只有一个整数,\(n\) 代表同学的数量。同学的学号从 \(1\) 到 \(n\) 编号。
接下来 \(n\) 行每行一个整数 \(a_k\) 表示同学 \(k\) 将要监视同学 \(a_k\),\(1 \le k \le n\),\(1 \le a_k \le n\),\(a_k \ne k\)。
输出格式
一个数,最多能有多少同学参加入这个任务。
输入输出样例 #1
输入 #1
6
2
3
1
3
6
5
输出 #1
3
说明/提示
对于 \(100\%\) 的数据,\(1\le k,a_k\le n\le 10^6\)。
基本思路
这题理应是一个基环树 \(DP\) 的,奈何内存给太小只有 \(32MB\)。
那么就换一种解法吧,先提取题目信息,如果一个人没有人留在教室监视的话就不能去搬书,那么可以转化为一个点如果没有入度的话就不可以选,还有每个点只有一条出边。
我第一反应原本是二分图或者网络流啥的,但这个数据范围即便对 \(Dinic\) 也有点极限,而且集合内部存在连边,网络流建模也没法建。
\(DP\) 肯定是没指望了,知道也写不出来,那就尝试贪心吧。
一开始就没有入度的点是肯定不可以选的,但它指向的儿子一定是可以选的,因为它的父亲不可能选。那么就得到一种贪心思想,就是一个点如果不选的话,那么一定选上它的儿子。
我找了半天找不到反例,那就感性理解一下,如果我们不选一个点,又不选它的儿子节点,那它的孙子节点选上是否会有更大贡献?如果都是在环上那肯定好说,所以我们就讨论在子树上的,好像除了把选的序列向下推了一点,那只会更差,并不会更优。
啊,注意一下,这里的 \(queue\) 并不代表我们选哪个或者不选哪个,而是储存哪些点已经被处理过了。
那么贡献一下第一个版本的代码吧
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6+10;
int n, a[N], degree[N], tot, res, ans;
int color[N];
queue<int>q;
int main() {
ios::sync_with_stdio(false);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
degree[a[i]]++;
}
tot = n;
for (int i = 1; i <= n; i++) {
if (degree[i] == 0) {
q.push(i);
color[i] = -1;
tot--;
}
}
while (!q.empty()) {
int u = q.front();
q.pop();
degree[a[u]]--;
if (color[u] == -1) {
if (color[a[u]] == 0)
tot--;
color[a[u]] = 1;
q.push(a[u]);
} else if (degree[a[u]] == 0 && color[a[u]] == 0) {
q.push(a[u]);
tot--;
color[a[u]] = -1;
}
}
res = 1;
while (tot) {//总数没清零,存在(裸)环
for (int i = res; i <= n; i++) {
if (color[i] == 0) {
q.push(i);//随便在环中取出一个断点
color[i] = -1;
res = i;//小小循环优化
tot--;
break;
}
}
while (!q.empty()) {//实际是从上面那一段Ctrl+C下来的
int u = q.front();
q.pop();
degree[a[u]]--;
if (color[u] == -1) {//这个节点不选
if (color[a[u]] == 0)
tot--;
color[a[u]] = 1;
q.push(a[u]);
} else if (degree[a[u]] == 0 && color[a[u]] == 0) {
q.push(a[u]);
tot--;
color[a[u]] = -1;
}
}
}
for (int i = 1; i <= n; i++)
if (color[i] == 1)
ans++;
cout << ans;
// 8
// 2 4 4 7 2 5 5 7
return 0;
}
这玩意只有 \(70pts\),是寻找独立的环那段出错了,我也不知道为什么,结论是越复杂越容易错。
那么改一下是第二个代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6+10;
int n, a[N], degree[N], tot, res, ans;
int color[N];
queue<int>q;
int main() {
ios::sync_with_stdio(false);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
degree[a[i]]++;
}
tot = n;
for (int i = 1; i <= n; i++) {
if (degree[i] == 0) {
q.push(i);
color[i] = -1;
}
}
while (!q.empty()) {
int u = q.front();
q.pop();
degree[a[u]]--;
if (color[u] == -1) {
color[a[u]] = 1;
q.push(a[u]);
} else if (degree[a[u]] == 0 && color[a[u]] == 0) {
q.push(a[u]);
color[a[u]] = -1;
}
}
for (int i = 1; i <= n; i++) {
if (color[i] == 0) {
assert(degree[i]);
color[i] = -1;
color[a[i]] = 1;
for (int j = a[i]; j != i; j = a[j]) {
if (color[a[j]] == 0) {//for循环整个取出
assert(color[j] != 0);
if (color[j] == 1)
color[a[j]] = -1;
else
color[a[j]] = 1;
}
}
}
}
for (int i = 1; i <= n; i++)
if (color[i] == 1)
ans++;
cout << ans;
return 0;
}
这次改用 \(for\) 来直接提环,貌似更加可靠了一点,但只有 \(90pts\),怎么回事呢?
看这里:
if (color[u] == -1) {
color[a[u]] = 1;
q.push(a[u]);
} else if (degree[a[u]] == 0 && color[a[u]] == 0) {
q.push(a[u]);
color[a[u]] = -1;
}
注意 \(color\) 数组是覆盖的方式修改的
这肯定出点啥毛病。
若有多个节点指向当前节点,而这些节点都没被选,是不是说有一个节点会被重复入队?
那么 \(AC\) 代码出炉了:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6+10;
int n, a[N], degree[N], ans;
int color[N];
queue<int>q;
int main() {
// freopen("gen4.in", "r", stdin);
ios::sync_with_stdio(false);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
degree[a[i]]++;//统计入度
}
for (int i = 1; i <= n; i++) {
if (degree[i] == 0) {
q.push(i);
color[i] = -1;//肯定不能选
}
}
while (!q.empty()) {
int u = q.front();
q.pop();
degree[a[u]]--;
if (color[u] == -1) {
if (color[a[u]] == 0)
q.push(a[u]);
color[a[u]] = 1;
} else if (degree[a[u]] == 0 && color[a[u]] == 0) {
q.push(a[u]);
color[a[u]] = -1;
}
}
for (int i = 1; i <= n; i++) {
if (color[i] == 0) {
color[i] = -1;
int j = i;
while (color[a[j]] == 0) {//更稳定的方法
if (color[j] == 1)
color[a[j]] = -1;
else
color[a[j]] = 1;
j = a[j];
}
}
}
for (int i = 1; i <= n; i++)
if (color[i] == 1)
ans++;
cout << ans;
// 8
// 2 4 4 7 2 5 5 7
return 0;
}

浙公网安备 33010602011771号