我们知道,如果一个无向连通图有
n
个结点和
这样一来,在图中,一定会有且只有一个环。
不难想到,最优的安排问题,往往是在 DP 的方向考虑。但是如果产生了环,肯定就有后效性。有后效性就不能做 DP。那怎么办呢?
很显然我们必须把环拿掉,至于删去环上的哪条边,其实都无所谓,因为删去后都能得到一棵正常的树。在新的树上就可以跑 DP 了。
先不管 DP 的问题,如何找出这个环?
对于多个环的情况,事后我也查阅了一些文章,但是这似乎是个 NP 问题…
参考:https://www.zhihu.com/question/32196067
http://blog.csdn.net/robin_xu_shuai/article/details/51898847
不过,本题当中只有一个环,那么就很好办了,可以从任意点开始跑一遍 dfs,如果搜到某一个已经被访问过的点,那么当前边为环上的边。当然,其实跑 bfs 也可以搞,而且还不用担心爆栈的问题,相对而言更加保险。反正我是用 bfs 做的。
现在得到这条多余的边之后,屏蔽它,得到一棵树。在这棵树上就可以做树形 DP。一开始我想成了“移动信号”,最后才发现:那题是照点,本题是照边。
其实每个点无非选或不选,不妨用
但是别忘了,还有一条被我们屏蔽了的边!不妨称为
参考代码:
#include <algorithm>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
const int maxn = 1e5 + 100;
struct Edge { int from, to, next; } edge[maxn << 1];
int cntEdge;
int head[maxn];
void addEdge(int u, int v) {
edge[++cntEdge].from = u;
edge[cntEdge].to = v;
edge[cntEdge].next = head[u];
head[u] = cntEdge;
}
int n;
int a[maxn];
int bridge;
bool vis[maxn];
int father[maxn];
void bfs() {
queue <int> q;
while (!q.empty()) q.pop();
q.push(1);
vis[1] = true;
while (!q.empty()) {
int cur = q.front(); q.pop();
for (int i = head[cur]; i; i = edge[i].next)
if (!vis[edge[i].to]) {
q.push(edge[i].to);
vis[edge[i].to] = true;
father[edge[i].to] = cur;
} else if (edge[i].to != father[cur]) { bridge = i; return; }
}
}
int dp[maxn][5];
void dfs1(int root, int pre) {
dp[root][1] = 1;
for (int i = head[root]; i; i = edge[i].next) {
if (i == bridge || edge[i].to == edge[bridge].from) continue; //遍历的时候不能看多余边
int child = edge[i].to;
if (child != pre) {
dfs1(child, root);
dp[root][0] += dp[child][1];
dp[root][1] += min(dp[child][0], dp[child][1]);
}
}
if (root == edge[bridge].to) dp[root][0] = INT_MAX; //根不取,则多余边的另一点必须取
}
void dfs2(int root, int pre) { //无限制
dp[root][1] = 1;
for (int i = head[root]; i; i = edge[i].next) {
if (i == bridge || edge[i].to == edge[bridge].from) continue;
int child = edge[i].to;
if (child != pre) {
dfs2(child, root);
dp[root][0] += dp[child][1];
dp[root][1] += min(dp[child][0], dp[child][1]);
}
}
}
int main(void) {
freopen("1832.in", "r", stdin);
freopen("1832.out", "w", stdout);
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
for (int j = 0; j < a[i]; j++) {
int to;
scanf("%d", &to);
addEdge(i, to);
}
}
bfs(); //求多余的边
memset(dp, 0, sizeof dp);
dfs1(edge[bridge].from, 0); //(新)根结点不取
int ans = dp[edge[bridge].from][0];
memset(dp, 0, sizeof dp);
dfs2(edge[bridge].from, 0); //(新)根结点取
printf("%d\n", min(ans, dp[edge[bridge].from][1]));
return 0;
}