您的位置:首页 > 其它

hdu3861 强连通+最小路径覆盖

2015-09-15 22:35 99 查看
题意:有 n 个点,m 条边的有向图,需要将这些点分成多个块,要求:如果两点之间有路径能够互相到达,那么这两个点必须分在同一块;在同一块内的任意两点相互之间至少要有一条路径到达,即 u 到达 v 或 v 到达 u;每个点都只能存在于单独一个块内。问最少需要划分多少块。

首先,对于如果两点之间能够相互到达则必须在同一块,其实也就是在同一个强连通分量中的点必须在同一块中,所以首先就是强连通缩点。然后在同一块内的任意两点之间要有一条路,那么其实就是对于一块内的强连通分量,至少要有一条路径贯穿所有分量。而这一条路径上的所有强连通分量就可以构成同一块。那么其实我们就是需要找出最少的这样的路径将所有点全部覆盖一遍,就是做一遍最小路径覆盖。

最小路径覆盖数=点数-拆点后的最大匹配数。

#include<stdio.h>
#include<string.h>
#include<stack>
#include<queue>
using namespace std;

const int maxn=10005;
const int maxm=2e5+5;

int head[2][maxn],point[2][maxm],nxt[2][maxm],size[2];
int n,t,scccnt;
int stx[maxn],low[maxn],scc[maxn];
int vis[maxn],match[maxn];
stack<int>S;

void init(){
memset(head,-1,sizeof(head));
size[0]=size[1]=0;
}

void add(int a,int b,int c=0){
point[c][size[c]]=b;
nxt[c][size[c]]=head[c][a];
head[c][a]=size[c]++;
}

void dfs(int s){
stx[s]=low[s]=++t;
S.push(s);
for(int i=head[0][s];~i;i=nxt[0][i]){
int j=point[0][i];
if(!stx[j]){
dfs(j);
low[s]=min(low[s],low[j]);
}
else if(!scc[j]){
low[s]=min(low[s],stx[j]);
}
}
if(low[s]==stx[s]){
scccnt++;
while(1){
int u=S.top();S.pop();
scc[u]=scccnt;
if(s==u)break;
}
}
}

void setscc(){
memset(stx,0,sizeof(stx));
memset(scc,0,sizeof(scc));
t=scccnt=0;
for(int i=1;i<=n;++i)if(!stx[i])dfs(i);
for(int i=1;i<=n;++i){
for(int j=head[0][i];~j;j=nxt[0][j]){
int k=point[0][j];
if(scc[i]!=scc[k]){
add(scc[i],scc[k]+scccnt,1);
}
}
}
}

int dfs1(int k){
for(int i=head[1][k];~i;i=nxt[1][i]){
if(!vis[point[1][i]]){
int p=point[1][i];
vis[p]=1;
if(match[p]==-1||dfs1(match[p])){
match[p]=k;
return 1;
}
}
}
return 0;
}

int main(){
int T;
scanf("%d",&T);
while(T--){
int m;
scanf("%d%d",&n,&m);
init();
while(m--){
int a,b;
scanf("%d%d",&a,&b);
add(a,b);
}
setscc();
int ans=0;
memset(match,-1,sizeof(match));
for(int i=1;i<=2*scccnt;++i){
memset(vis,0,sizeof(vis));
if(dfs1(i)==1)ans++;
}
printf("%d\n",scccnt-ans);
}
return 0;
}


View Code
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: