Description
给出一个n+1个点n条边的树,其中每一个度数为1的点为出口。
现在有一些点有逃犯,你需要在一些没有逃犯的点放置警卫,有警卫的点逃犯无法经过。
求若使所有逃犯均无法到达出口,最少需要多少个警卫。
n<=10^5
Solution
为什么我一眼想到最小割=w=
就是所有的逃犯无法到达一些点,那么我们把每个点拆点,x向x’连容量为1的边,割掉这条边表示在这个点放置警卫。
然后对于原图中的每一条边(x,y),从x’向y连容量为∞的边,表示这条边无法被割掉。
同理从S向每个有逃犯的x’点连容量为∞的边,因为无法在有逃犯的点放置警卫。
然后从每个出口的x’点向T连容量为∞的边,因为可以在出口放置警卫。
然后跑一边最小割就是答案了。如果最小割为∞就是无解。
200000个点的最大流什么的真是梦想_ (:зゝ∠)_
然而它碾过去了2333
好吧我们来讨论正解=w=
随便选一个度数为1的点做根节点,那么所有的出口都是根节点或者叶子节点了。
考虑从下往上递推,每个点可以有三种情况
0:这个点为根的子树中所有的逃犯都无法到达这个点,且不存在一条从这点到叶子节点的路径。
1:这个点为根的子树中所有的逃犯都无法到达这个点,但存在一条从这点到叶子节点的路径。
2:这个点为根的子树中所有的逃犯有可能到达这个点,且不存在一条从这点到叶子节点的路径。
那么考虑一个点,如果它的所有儿子都被封死了(就是状态0),那么它自己也就被封死了。
如果这个点有逃犯,那么这个点一下的1点都必须被封死,要不然逃犯可以通过这个点到叶子节点,所以答案加上当前节点状态为1的儿子个数。
否则如果当前点既有状态为1的儿子,又有状态为2的儿子,那么2的逃犯就可以到达这个点,然后从这个点往下逃走。
所以这个点必须被封死。
否则就是只有0\1,0\2的情况,那么除0外是几这个点的状态就是几。
最后如果根节点的状态为2根节点也要封死。
Code
最小割
#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define rep(i,a) for(int i=last[a];i;i=next[i])
using namespace std;
const int N=2*1e5+5,inf=1e5;
int n,m,S,T,l,x,y,ans,dis[N],d[N],r[N];
int t[N*8],next[N*8],f[N*8],last[N];
void add(int x,int y,int z) {
t[++l]=y;f[l]=z;next[l]=last[x];last[x]=l;
t[++l]=x;f[l]=0;next[l]=last[y];last[y]=l;
}
bool bfs() {
memset(dis,0,sizeof(dis));dis[S]=1;
int i=0,j=1;d[1]=S;
while (i<j) rep(k,d[++i])
if (!dis[t[k]]&&f[k]) dis[t[k]]=dis[d[i]]+1,d[++j]=t[k];
return dis[T];
}
int dinic(int x,int y) {
if (x==T) return y;
int now=0;
rep(i,x) if (f[i]&&dis[t[i]]==dis[x]+1) {
int k=dinic(t[i],min(y,f[i]));
y-=k;now+=k;f[i]-=k;f[i^1]+=k;
if (!y) break;
}
if (!now) dis[x]=-1;
return now;
}
int main() {
scanf("%d%d",&n,&m);n++;S=0;T=2*n+1;l=1;
fo(i,1,n-1) {
scanf("%d%d",&x,&y);x++;y++;
add(x+n,y,inf);add(y+n,x,inf);
r[x]++;r[y]++;
}
fo(i,1,m) scanf("%d",&x),x++,add(S,x+n,inf);
fo(i,1,n) if (r[i]==1) add(i+n,T,inf);
fo(i,1,n) add(i,i+n,1);
while (bfs()) ans+=dinic(S,inf);
if (ans>=inf) printf("-1\n");
else printf("%d\n",ans);
}
Dp
#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define rep(i,a) for(int i=last[a];i;i=next[i])
using namespace std;
const int N=1e5+5;
int n,m,l,x,y,ans,d[N],f[N];
int t[N*2],next[N*2],last[N];
bool bz[N];
void add(int x,int y) {
t[++l]=y;next[l]=last[x];last[x]=l;
}
void dfs(int x,int y) {
int s[3];memset(s,0,sizeof(s));
rep(i,x) if (t[i]!=y) {
dfs(t[i],x);
s[f[t[i]]]++;
}
if (bz[x]) {
ans+=s[1];
f[x]=2;
} else if (s[1]&&s[2]) {
ans++;
f[x]=0;
} else if (s[1]) f[x]=1;
else if (s[2]) f[x]=2;
else if (s[0]) f[x]=0;
}
int main() {
scanf("%d%d",&n,&m);n++;
fo(i,1,n-1) {
scanf("%d%d",&x,&y),x++,y++;
add(x,y),add(y,x);
d[x]++;d[y]++;
}
fo(i,1,n) f[i]=1;
fo(i,1,m) {
scanf("%d",&x),x++;
if (d[x]==1) {printf("-1\n");return 0;}
bz[x]=1;
}
fo(i,1,n) if (d[i]==1) {
dfs(i,0);
if (f[i]==2) ans++;
break;
}
printf("%d\n",ans);
}