题解:P1543 [POI2004] SZP

· · 题解

题意

在一个基环树森林上,选出一些点,要求每一个选出的点都至少有一个没被选出的父节点。问最多可以选多少个。

思路

对于每一个入度为零的点,因为一定是没有点去监督他的,所以一定不选。再基于贪心的思想,这个点不选了,那么他的儿子结点就成了至少有一个没被选出的父节点的状态,所以是一定要选的。

对于上面的操作,可以用类似拓扑排序的方法,再拓扑的过程中,去维护要选的点个数。

做完拓扑排序后,剩下的入度仍不为零的点就是环以及单点了。对于环,还是贪心,隔一个选一个是最优的,所以这些的可选点个数就是环的长度下取整;至于单点,没有可选的。

坑点?

有个地方一开始我写错了,调了好久才改出来,错法较为降智,但还是挂出来,希望大家不要像我一样喵。

    for (int i = 1; i <= n; i++) {
        if (vis[i] == 0 && in[i] > 0) {
            huan_sum ++;
        }
    }
    ans += huan_sum / 2;

这是我在处理环时的代码,可以发现,这只猫猫就只是统计了所有在环上点的总个数,最后将个数除二加入答案。

明显是不对的啊!每一个环的可选点都是这一个环的长度除二,这样的弱智操作,只要有两个奇数环就寄了。

但是大气的洛谷还是给了我一个不错的分数呢。

link

code

#include<bits/stdc++.h>
#define ll long long 
#define il inline

using namespace std;
/*快读省了*/
ll n, a[1001000], ans;
ll to[1001000], cnt, in[1001000], vis[1001000], choose[1001000];

il void toposort() {
    queue<ll> q;
    for (int i = 1; i <= n; i++) 
        if (in[i] == 0) // 
            q.push(i);
    ll go_out = 0, huan_sum = 0;
    while(!q.empty()) {
        ll x = q.front();
        q.pop();
        vis[x] = 1; // 标记不在环中
        if (choose[x] == 1) { // 需要选
            in[to[x]] --;
            if (in[to[x]] == 0) q.push(to[x]); 
        } else {
            if (!choose[to[x]]) { // 防止重复统计
                q.push(to[x]);
                go_out ++;
            }
            choose[to[x]] = 1;
        }
    }
    ans += go_out;
    for (int i = 1; i <= n; i++) {
        huan_sum = 0;
        if (vis[i] == 0 && in[i] > 0) { // 不要像我一样错了
            for (int k = i ; vis[k] == 0; k = to[k]) {
                huan_sum ++;
                vis[k] = 1; 
            }
            ans += huan_sum / 2;
        }
    }
    return ;
}

int main() {
    read(n);
    for(int i = 1; i <= n; i++) {
        read(a[i]);
        to[i] = a[i];
        in[a[i]] ++;
    }
    toposort();
    cout << ans << "\n";
    return 0; // 完结撒花
}