感觉这个题很难,之前想了很久,一个tree-dp。
题目大大意:
(N + 1)个点,N条边的树。边代表走廊,节点代表监狱。但是监狱门都开了,有些节点有罪犯,他们可以沿着树边(走廊)任意移动,如果他们至少有一个人能走到叶子节点,他们就越狱了。你需要安排M个狱警,他们只能被安排再树节点上,并且这些节点最开始是没有罪犯的。狱警不能移动,罪犯走到叶子的路径上的节点如果有狱警,他就无法通过该节点,求至少要几个狱警才能让所有罪犯都无法越狱? 无解输出-1。
输入的是两个数组,代表树的边,以及罪犯起初所在的节点编号。
如图:int solution(vector<int> &A, vector<int> &B, vector<int> &C);
对应的输入是:
A[0] = 0 B[0] = 1 C[0] = 1 A[1] = 1 B[1] = 2 C[1] = 6 A[2] = 2 B[2] = 3 A[3] = 3 B[3] = 4 A[4] = 3 B[4] = 5 A[5] = 2 B[5] = 6 A[6] = 6 B[6] = 8 A[7] = 6 B[7] = 7
我们可以在0,2,7,8 或者 0,3,7,8节点安排狱警达到目的,所以返回4。
数据范围:
N [1..200000]
罪犯数 [0..N + 1]
节点编号 [0..N]整数
A,B是树边,C是罪犯所在节点。
要求复杂度: 时间空间都是O(N)。
这是一个显然的树型dp。当然,我们可以定义dp[x]控制住表示以x根的子树所需要的最少狱警数。但是,什么叫做控制住?比如1-2-3 一个链,罪犯在1,根是1(1上面还有,不是叶子),我在2放一个狱警,显然这棵树满足条件了,1到不了3了,然而1还可能通过上面的路径“绕”出去。所以我们这么表示状态是不够的。我们需要考虑,罪犯是否能到当前的根节点,以及从当前根节点到叶子是否有通路——不然即使局部控制住了,“上面”可能还有罪犯下来。例如:
1-2-3-4 这条链 1 是根,3有罪犯,我在2,4都放了狱警,这样罪犯无法上下。但是假设还有一条路是1-5-6,没有一个点有罪犯,显然在这样的子树上,这条路没必要堵住,然而,在整个树上,其他节点的罪犯可能通过这条路到达叶子。
于是,两个条件,我们一共有4种情况,然而有一种情况是罪犯能到达当前的根节点且从当前的根到叶子有通路,这种情况罪犯显然先能走到根,再从根能走到叶子了,这连自己的子树都没控制住。 所以我们应该有3种状态, dp[00][x] , dp[01][x], dp[11][x], 用十进制就是dp[0][x],dp[1][x],dp[2][x],他们对应的意义是:
以x为根的子树上的罪犯无法到达叶子节点,
但是:
00 逃犯无到根的路 从根到叶子无通路
01 逃犯无到根的路 从根到叶子可能有通路
10 逃犯可能有到根的路 从根到叶子无通路
这3种情况下需要狱警的最少数目。
显然 ,0需要的狱警个数 >= 1和2的狱警个数。
然后我们只要考虑dp[state][x]就好了,一般dp结构都是假设孩子都算好了,用孩子表示根,对于这个题dp[state][x]可以由同样state的孩子来推,可以仔细推一下,基本上就是dp[state][x] = sum of {dp[state][son of x]}
这是有一个特殊的就是 我们要不要在当前节点根放一个狱警?如果我们在当前节点放一个狱警,显然状态就直接变成0了,因为把根堵上了,这时孩子可以“灵活些”,推0的时候,孩子的状态可以是0,也可以是1,也可以是2....这事唯一的例外,每个孩子节点的状态可以任意,这时由状态定义本身决定的,其他就简单了。
当然还要注意一些细节,例如,罪犯本身在叶子节点,这显然就不行了……。
代码:
/* 00 逃犯无到根的路 从根到叶子无通路 01 逃犯无到根的路 从根到叶子有通路 10 逃犯有到根的路 从根到叶子无通路 0 >= 2 0 >= 1 */ void make(int &x,int y) { if ((y >= 0) && ((x < 0) || (x > y))) { x = y; } } int give(int now,vector<vector<int> > &con,vector<vector<int> > &have,int father,int w1,int w2) { int sum = 0; for (int i = 0; i < con[now].size(); ++i) { if (father != con[now][i]) { int temp = have[w1][con[now][i]]; if (w2 >= 0) { make(temp, have[w2][con[now][i]]); } if (temp < 0) { return -1; } sum += temp; } } return sum; } void dfs(vector<bool> &mark,vector<vector<int> > &con,vector<vector<int> > &have,int now,int father) { if (con[now].size() == 1) { have[0][now] = 1; have[1][now] = 0; have[2][now] = 1; return; } for (int i = 0; i < con[now].size(); ++i) { if (father != con[now][i]) { dfs(mark,con,have,con[now][i],now); } } have[2][now] = give(now, con, have, father, 2, -1); if (mark[now]) { have[0][now] = have[1][now] = -1; return; } have[0][now] = give(now, con, have,father, 0, -1); int may = give(now, con, have, father,1 , 2); if (may >= 0) { make(have[0][now], may + 1); } have[1][now] = give(now, con, have,father, 1, -1); make(have[1][now], have[0][now]); make(have[2][now], have[0][now]); } int solution(vector<int> &A, vector<int> &B, vector<int> &C) { // write your code in C++11 if (C.empty()) { return 0; } int n = A.size() + 1; if (n == 2) { return -1; } vector<vector<int> > con(n); for (int i = 0; i < A.size(); ++i) { con[A[i]].push_back(B[i]); con[B[i]].push_back(A[i]); } vector<bool> mark(n, false); for (int i = 0; i < C.size(); ++i) { mark[C[i]] = true; if (con[C[i]].size() == 1) { return -1; } } vector<vector<int> > have(3); have[0].resize(n); have[1].resize(n); have[2].resize(n); dfs(mark,con,have,C[0],-1); return have[2][C[0]]; }