连通图

原题链接:https://cn.vjudge.net/article/371
ps:为了偷懒,以下建图都是用vector,悄悄和用链式前向星的对比下,(大部分情况下)发现链式前向星用的时间少了一半。

Network of Schools

POJ - 1236
题意:给一个有向图,求最少需要加几个网络到结点,使得这些网络能到达任意一个结点;以及最少需要加入多少个连边,使得图的任意一个点,可达任意其他点。
题解:先缩点,缩点后得到的树求其入度为0、出度为0的结点数in0、out0;答案就是in0和max(in0,out0),特判下只有一个连通分量的情况。

#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn=110;

vector<int> ve[maxn];
int n,cn;
int dfn[maxn],low[maxn],cnt;
int in[maxn],out[maxn],color[maxn];
stack<int> s;
bool ins[maxn];
void init()
{
	memset(dfn,0,sizeof(dfn));
	memset(low,0,sizeof(low));
	memset(ins,0,sizeof(ins));
	cn=cnt=0;
	memset(in,0,sizeof(in));
	memset(out,0,sizeof(out));
	for(int i=1;i<=n;i++) ve[i].clear();
	while(!s.empty()) s.pop();
}
void tarjan(int u)
{
	low[u]=dfn[u]=++cnt;
	s.push(u);
	ins[u]=1;
	for(int i=0;i<ve[u].size();i++){
		int v=ve[u][i];
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}else if(ins[v]){//
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(low[u]==dfn[u]){
		cn++;
		while(s.top()!=u){
			ins[s.top()]=0;
			color[s.top()]=cn;
			s.pop();
		}
		color[u]=cn;
		ins[u]=0;s.pop();
	}
}

int main()
{
	while(~scanf("%d",&n)){
		int v;
		init();
		for(int u=1;u<=n;u++){
			while(~scanf("%d",&v)){
				if(!v) break;
				ve[u].push_back(v);
			}
		}
		
		for(int i=1;i<=n;i++)
			if(!dfn[i])
				tarjan(i);
			
		for(int u=1;u<=n;u++){
			for(int i=0;i<ve[u].size();i++){
				int v=ve[u][i];
				if(color[u]!=color[v]){//注意这里 
					in[color[v]]++;
					out[color[u]]++;
				}
			}
		}
		int in0=0,out0=0;
		for(int i=1;i<=cn;i++){
			if(!in[i]) in0++;
			if(!out[i]) out0++; 
		}
		if(cn==1){//当只有一个连通分量时,特判 
			printf("%d\n0\n",in0);
			continue;
		}
		printf("%d\n%d\n",in0,max(in0,out0));
	}
	return 0;
}

Network

UVA - 315
题意:给一个无向图(direct line:直达线),求割点数。
题解:割点:
1.若为根结点,且子树数大于1
2.若为非根结点,则父子边u->v满足dfn[u]<=low[v]
注意的地方:图我用vector储存,对于第1点,不能直接用ve[root].size()来看,因为可能有重边的情况;对于第2点,不能每次遇到dfn[u]<=low[v]就直接ans++,因为u可能重复计数了。

#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn=110;

vector<int> ve[maxn];
int n;
int dfn[maxn],low[maxn],cnt;
bool vis[maxn];
int fa[maxn];

void init()
{
	memset(dfn,0,sizeof(dfn));
	memset(low,0,sizeof(low));
	cnt=0;
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=n;i++) ve[i].clear();
}
void tarjan(int u,int p)
{
	low[u]=dfn[u]=++cnt;
	fa[u]=p;
	for(int i=0;i<ve[u].size();i++){
		int v=ve[u][i];
		if(!dfn[v]){
			tarjan(v,u);
			low[u]=min(low[u],low[v]);
		}else if(v!=p){
			low[u]=min(low[u],dfn[v]);
		}
	}
}

int main()
{
	while(~scanf("%d",&n)){
		if(!n) break;
		int u,v;
		char ch;
		init();
		while(~scanf("%d",&u)){
			if(!u) break;
			while(~scanf("%d%c",&v,&ch)){
				ve[u].push_back(v);
				ve[v].push_back(u);
				if(ch=='\n') break;
			}
		}
		for(int i=1;i<=n;i++)
			if(!dfn[i])
				tarjan(i,-1);
		int num=0; 
		for(int i=2;i<=n;i++){
			if(fa[i]==1) num++;
			if(fa[i]!=-1&&fa[i]!=1&&dfn[fa[i]]<=low[i])
				vis[fa[i]]=1;
		}
		int ans=0;
		if(num>1) ans++;//考虑重边 不能直接用ve[1].size() 
		//if(ve[1].size()>1) ans++;
		for(int i=2;i<=n;i++)
			if(vis[i]) ans++;
		printf("%d\n",ans);
	}
	return 0;
}

Critical Links

UVA - 796
给定一个无向图,求割边数,割边的性质:low(v)>dfn(u) 计算时要考虑重边,注意去重。
sb了,结点从0开始的

#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn=1010;

vector<int> ve[maxn];
int n;
int dfn[maxn],low[maxn],cnt;
bool vis[maxn];
int fa[maxn];

struct node{
	int u,v;
	bool operator == (const node &b)
	{
		return u==b.u&&v==b.v;
	}
}cut[maxn*210];
int tot;
bool cmp(const node& a,const node& b)
{
	if(a.u==b.u) return a.v<b.v;
	return a.u<b.u; 
}
void init()
{
	memset(dfn,0,sizeof(dfn));
	memset(low,0,sizeof(low));
	cnt=tot=0;
	memset(vis,0,sizeof(vis));
	for(int i=0;i<n;i++) ve[i].clear();
}

void tarjan(int u,int p)
{
	low[u]=dfn[u]=++cnt;
	fa[u]=p;
	for(int i=0;i<ve[u].size();i++){
		int v=ve[u][i];
		if(!dfn[v]){
			tarjan(v,u);
			low[u]=min(low[u],low[v]);
			if(low[v]>dfn[u]){
				cut[tot].u=u;
				cut[tot].v=v;
				if(cut[tot].u>cut[tot].v)
					swap(cut[tot].u,cut[tot].v);
				tot++;
			}
		}else if(v!=p){
			low[u]=min(low[u],dfn[v]);
		}
		
	}
}

int main()
{
	while(~scanf("%d",&n)){
		int u,v,num;
		init();
		for(int i=1;i<=n;i++){
			scanf("%d",&u);
			scanf(" (%d)",&num);
			while(num--){
				scanf("%d",&v);
				ve[u].push_back(v);
				ve[v].push_back(u);
			}
		}
		for(int i=0;i<n;i++)
			if(!dfn[i])
				tarjan(i,-1);
		sort(cut,cut+tot,cmp);
		tot=unique(cut,cut+tot)-cut;
		
		printf("%d critical links\n",tot);
		for(int i=0;i<tot;i++){
			printf("%d - %d\n",cut[i].u,cut[i].v);
		}
		printf("\n");
	}
	return 0;
}

Network

POJ - 3694
题意:给一个无向图,q个查询,问加入边后,当前树的割边数。
题解:先对每个强连通分量缩点,缩点后得到一棵树,如果这树的结点数为ans,那么割边数就是ans-1。对于每次加边操作,我们把这两个点之间的结点都union起来即可,然后根据找当前树的结点数ans,来得到当前割边数ans-1。
对于缩点,除了用常规的stack方法,还可以用并查集。
并查集实现:

#include
#include
#include
#include
#include
using namespace std;
const int maxn=100010;


int low[maxn],dfn[maxn];
vector<int> ve[maxn];
struct node{
	int u,v;
}cut[maxn];
int tot,cnt;

int n,m,q;
int f[maxn],f2[maxn];
bool vis[maxn];//dfs
void init()
{
	memset(low,0,sizeof(low));
	memset(dfn,0,sizeof(dfn));
	memset(vis,0,sizeof(vis));
	tot=cnt=0;
	for(int i=0;i<=n;i++){
		ve[i].clear();
		f[i]=f2[i]=i;
	}
}
int find1(int x,int f[])
{
	return x==f[x]?x:f[x]=find1(f[x],f);
}
void union1(int x,int y,int f[])
{
	int fx=find1(x,f),fy=find1(y,f);
	if(fx!=fy)
		f[fx]=fy;
}
void tarjan(int u,int p)
{
	low[u]=dfn[u]=++cnt;
	for(int i=0;i<ve[u].size();i++){
		int v=ve[u][i];
		if(!dfn[v]){
			tarjan(v,u);
			low[u]=min(low[u],low[v]);
			if(low[v]>dfn[u]){
				cut[tot].u=u;
				cut[tot].v=v;
				tot++;
			}else{
				union1(u,v,f);
			}
		}else if(v!=p){
			low[u]=min(low[u],dfn[v]);
		}
	}
}
bool dfs(int u,int v)
{
	vis[u]=1;
	if(u==v) return 1;
	for(int i=0;i<ve[u].size();i++){
		int x=ve[u][i];
		if(vis[x]) continue;
		if(dfs(x,v)){
			union1(u,x,f2);
			return 1;
		}
	}
	return 0;
}
int main()
{
	int cas=1;
	while(~scanf("%d%d",&n,&m)){
		if(!n&&!m) break;
		init(); 
		int u,v;
		for(int i=0;i<m;i++){
			scanf("%d%d",&u,&v);
			ve[u].push_back(v);
			ve[v].push_back(u);
		}
		for(int i=1;i<=n;i++)
			if(!dfn[i]) tarjan(i,0);
		vector<int> cur;
		cur.clear();
		//for(int i=1;i<=n;i++) printf("f[%d]:%d\n",i,f[i]);
		for(int i=1;i<=n;i++)//把当前树结点存储起来,更新后的树结点都在这里 
			if(find1(i,f)==i){
				cur.push_back(i);
				//printf("root: %d\n",i);
			}
		
		//缩点 建树 
		for(int i=0;i<=n;i++) ve[i].clear();//clear 
		for(int i=0;i<tot;i++){
			u=cut[i].u;
			v=cut[i].v;
			u=find1(u,f);//在新树的编号 
			v=find1(v,f);
			ve[u].push_back(v);
			ve[v].push_back(u);
		}
		
		scanf("%d",&q);
		printf("Case %d:\n",cas++);
		while(q--){
			scanf("%d%d",&u,&v);
			u=find1(u,f);
			v=find1(v,f);
			memset(vis,0,sizeof(vis));
			dfs(u,v);
			int ans=0;
			for(int i=0;i<cur.size();i++){
				v=cur[i];
				if(v==find1(v,f2)) ans++;
			}
			printf("%d\n",ans-1);
		}
	}
	return 0;
}

stack缩点实现(不知为啥RE 留坑)。

#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn=100010;//


int low[maxn],dfn[maxn];
vector<int> ve[maxn];
struct node{
	int u,v;
}cut[maxn];
int tot,cnt;
stack<int> s;
bool ins[maxn];
int color[maxn],cn;
int n,m,q;
int f[maxn];
bool vis[maxn];//dfs
void init()
{
	memset(low,0,sizeof(low));
	memset(dfn,0,sizeof(dfn));
	memset(vis,0,sizeof(vis));
	memset(ins,0,sizeof(ins));
	cn=tot=cnt=0;
	for(int i=0;i<=n;i++){
		ve[i].clear();
		f[i]=i;
	}
	while(!s.empty()) s.pop();
}
int find1(int x)
{
	return x==f[x]?x:f[x]=find1(f[x]);
}
void union1(int x,int y)
{
	int fx=find1(x),fy=find1(y);
	if(fx!=fy)
		f[fx]=fy;
}
void tarjan(int u,int p)
{
	low[u]=dfn[u]=++cnt;
	s.push(u);
	ins[u]=1;
	for(int i=0;i<ve[u].size();i++){
		int v=ve[u][i];
		if(!dfn[v]){
			tarjan(v,u);
			low[u]=min(low[u],low[v]);
			if(low[v]>dfn[u]){
				cut[tot].u=u;
				cut[tot].v=v;
				tot++;
			}//else
			//	union1(u,v,f);
			
		}else if(v!=p){
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(low[u]==dfn[u]){
		cn++;
		while(s.top()!=u){
			ins[s.top()]=0;
			color[s.top()]=cn;
			s.pop();
		}
		color[u]=cn;ins[u]=0;
		s.pop();
	}
}
bool dfs(int u,int v)
{
	vis[u]=1;
	if(u==v) return 1;
	for(int i=0;i<ve[u].size();i++){
		int x=ve[u][i];
		if(vis[x]) continue;
		if(dfs(x,v)){
			union1(u,x);
			return 1;
		}
	}
	return 0;
}
int main()
{
	int cas=1;
	while(~scanf("%d%d",&n,&m)){
		if(!n&&!m) break;
		init(); 
		int u,v;
		for(int i=0;i<m;i++){
			scanf("%d%d",&u,&v);
			ve[u].push_back(v);
			ve[v].push_back(u);
		}
		for(int i=1;i<=n;i++)
			if(!dfn[i]) tarjan(i,0);
		
		
		//缩点 建树 
		for(int i=0;i<=n;i++) ve[i].clear();//clear 
		for(int i=0;i<tot;i++){
			u=cut[i].u;
			v=cut[i].v;
			u=color[u];
			v=color[v];
			ve[u].push_back(v);
			ve[v].push_back(u);
		}
		
		scanf("%d",&q);
		printf("Case %d:\n",cas++);
		while(q--){
			scanf("%d%d",&u,&v);
			u=color[u];
			v=color[v];
			memset(vis,0,sizeof(vis));
			dfs(u,v);
			int ans=0;
			for(int i=1;i<=cn;i++){			
				if(i==find1(i)) ans++;
			}
			printf("%d\n",ans-1);
		}
	}
	return 0;
}

Redundant Paths

POJ - 3177
题意:给一个有向图,求最少加入多少边,使得原图的任意两点的路径数有至少2条;即加入边后图的割边数为0,也即加入边后图为双连通图。
题解:缩点得到一棵树(无向),问题转化为加入多少边,使得树为0割边(双连通)。则加入的边数为 (度数为1的结点数+1)/2。

#include
#include
#include
#include
#include
using namespace std;
const int maxn=5010;


int low[maxn],dfn[maxn];
vector<int> ve[maxn];
struct node{
	int u,v;
}cut[maxn];
int tot,cnt;

int n,m,q;
int f[maxn];
bool vis[maxn];//dfs
void init()
{
	memset(low,0,sizeof(low));
	memset(dfn,0,sizeof(dfn));
	memset(vis,0,sizeof(vis));
	tot=cnt=0;
	for(int i=0;i<=n;i++){
		ve[i].clear();
		f[i]=i;
	}
}
int find1(int x)
{
	return x==f[x]?x:f[x]=find1(f[x]);
}
void union1(int x,int y)
{
	int fx=find1(x),fy=find1(y);
	if(fx!=fy)
		f[fx]=fy;
}
void tarjan(int u,int p)
{
	low[u]=dfn[u]=++cnt;
	for(int i=0;i<ve[u].size();i++){
		int v=ve[u][i];
		if(!dfn[v]){
			tarjan(v,u);
			low[u]=min(low[u],low[v]);
			if(low[v]>dfn[u]){
				cut[tot].u=u;
				cut[tot].v=v;
				tot++;
			}else{
				union1(u,v);
			}
		}else if(v!=p){
			low[u]=min(low[u],dfn[v]);
		}
	}
}

int main()
{
	while(~scanf("%d%d",&n,&m)){
		init(); 
		int u,v;
		for(int i=0;i<m;i++){
			scanf("%d%d",&u,&v);
			ve[u].push_back(v);
			ve[v].push_back(u);
		}
		for(int i=1;i<=n;i++)
			if(!dfn[i]) tarjan(i,0);
		//缩点 建树 
		for(int i=0;i<=n;i++) ve[i].clear();//clear 
		for(int i=0;i<tot;i++){
			u=cut[i].u;
			v=cut[i].v;
			u=find1(u);//在新树的编号 
			v=find1(v);
			ve[u].push_back(v);
			ve[v].push_back(u);
		}
		int ans=0;
		for(int i=1;i<=n;i++){
			if(ve[i].size()==1){
				ans++;
			}
		}
		printf("%d\n",(ans+1)/2);
	}
	return 0;
}

Warm up

HDU - 4612
题意:给一个无向图,求加入最少的一条边,使得剩下的割边(桥)最少。
题解:缩点,缩点后得到的树,对树求直径,则直径就是最多能减少的割边。
坑点:有重边,这题用vector莫得做了…
缩点方法,用并查集:

#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn=200010;


int low[maxn],dfn[maxn];
int tot1,head[maxn];
struct edge{
	int v,nxt;
	bool flag;
}e[maxn*10];
struct node{
	int u,v;
}cut[maxn*10];
int tot,cnt;

int n,m,q;
int f[maxn];

void init()
{
	memset(low,0,sizeof(low));
	memset(dfn,0,sizeof(dfn));
	memset(vis,0,sizeof(vis));
	memset(head,-1,sizeof(head));
	
	tot1=tot=cnt=0;
	for(int i=0;i<=n;i++){
		f[i]=i;
	}
}
void addedge(int u,int v)
{
	e[tot1].v=v;e[tot1].nxt=head[u];
	e[tot1].flag=0;head[u]=tot1++;
}
int find1(int x)
{
	return x==f[x]?x:f[x]=find1(f[x]);
}
void union1(int x,int y)
{
	int fx=find1(x),fy=find1(y);
	if(fx!=fy)
		f[fx]=fy;
}
void tarjan(int u,int p)
{
	low[u]=dfn[u]=++cnt;
	for(int i=head[u];~i;i=e[i].nxt){
		int v=e[i].v;
		if(e[i].flag) continue;
		e[i].flag=e[i^1].flag=1;
		if(!dfn[v]){
			tarjan(v,u);
			low[u]=min(low[u],low[v]);
			if(low[v]>dfn[u]){
				cut[tot].u=u;
				cut[tot].v=v;
				tot++;
			}else{
				union1(u,v);
			}
		}else{
			low[u]=min(low[u],dfn[v]);
		}
	}
}
int mx;
int dfs(int u,int fa)
{
	int tmp=0;
	for(int i=head[u];~i;i=e[i].nxt){
		int v=e[i].v;
		if(v==fa) continue;
		int d=dfs(v,u);
		mx=max(mx,d+tmp);
		tmp=max(tmp,d);
	}
	return tmp+1;
}

int main()
{
	while(~scanf("%d%d",&n,&m)){
		if(!n&&!m) break;
		init(); 
		int u,v;
		for(int i=0;i<m;i++){
			scanf("%d%d",&u,&v);
			addedge(u,v);
			addedge(v,u);
		}
		for(int i=1;i<=n;i++)
			if(!dfn[i]) tarjan(i,0);
		//缩点 建树 
		memset(head,-1,sizeof(head));//clear 
		tot1=0; 
		for(int i=0;i<tot;i++){
			u=cut[i].u;
			v=cut[i].v;
			u=find1(u);//在新树的编号 
			v=find1(v);
			addedge(u,v);
			addedge(v,u);
		}
		if(!tot){
			printf("0\n");
		}else{
			mx=0;
			dfs(find1(cut[0].u),-1);
			printf("%d\n",tot-mx);
		}
	}
	return 0;
}

用stack缩点,与上一题Poj3694相比,能过。

#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn=200010;


int low[maxn],dfn[maxn];
int tot1,head[maxn];
struct edge{
	int v,nxt;
	bool flag;
}e[maxn*10];
struct node{
	int u,v;
}cut[maxn*10];
int tot,cnt;

int n,m,q;
int f[maxn];

void init()
{
	memset(low,0,sizeof(low));
	memset(dfn,0,sizeof(dfn));
	memset(vis,0,sizeof(vis));
	memset(head,-1,sizeof(head));
	
	tot1=tot=cnt=0;
	for(int i=0;i<=n;i++){
		f[i]=i;
	}
}
void addedge(int u,int v)
{
	e[tot1].v=v;e[tot1].nxt=head[u];
	e[tot1].flag=0;head[u]=tot1++;
}
int find1(int x)
{
	return x==f[x]?x:f[x]=find1(f[x]);
}
void union1(int x,int y)
{
	int fx=find1(x),fy=find1(y);
	if(fx!=fy)
		f[fx]=fy;
}
void tarjan(int u,int p)
{
	low[u]=dfn[u]=++cnt;
	for(int i=head[u];~i;i=e[i].nxt){
		int v=e[i].v;
		if(e[i].flag) continue;
		e[i].flag=e[i^1].flag=1;
		if(!dfn[v]){
			tarjan(v,u);
			low[u]=min(low[u],low[v]);
			if(low[v]>dfn[u]){
				cut[tot].u=u;
				cut[tot].v=v;
				tot++;
			}else{
				union1(u,v);
			}
		}else{
			low[u]=min(low[u],dfn[v]);
		}
	}
}
int mx;
int dfs(int u,int fa)
{
	int tmp=0;
	for(int i=head[u];~i;i=e[i].nxt){
		int v=e[i].v;
		if(v==fa) continue;
		int d=dfs(v,u);
		mx=max(mx,d+tmp);
		tmp=max(tmp,d);
	}
	return tmp+1;
}

int main()
{
	while(~scanf("%d%d",&n,&m)){
		if(!n&&!m) break;
		init(); 
		int u,v;
		for(int i=0;i<m;i++){
			scanf("%d%d",&u,&v);
			addedge(u,v);
			addedge(v,u);
		}
		for(int i=1;i<=n;i++)
			if(!dfn[i]) tarjan(i,0);
		//缩点 建树 
		memset(head,-1,sizeof(head));//clear 
		tot1=0; 
		for(int i=0;i<tot;i++){
			u=cut[i].u;
			v=cut[i].v;
			u=find1(u);//在新树的编号 
			v=find1(v);
			addedge(u,v);
			addedge(v,u);
		}
		if(!tot){
			printf("0\n");
		}else{
			mx=0;
			dfs(find1(cut[0].u),-1);
			printf("%d\n",tot-mx);
		}
	}
	return 0;
}

Strongly connected

HDU - 4635
题意:给定一个有向图,问加入最多多少边,这个图仍然是不是强连通的,如果这个图本身就是强连通的,输出-1。
题解:缩点,建图,对于新的图,记录每个新点包含点的个数,从入度为0或出度为0的新点中找出包含点数最小的minnum,再用上面剩余的边ans - minnum*(n-minnum)就是所要的答案。
(PS:缩点时用stack方法来染色,用并查集在此题不合适,因为是有向图)

#include
#include
#include
#include
#include
#include
using namespace std;
#define ll long long
const int maxn=100010;


int low[maxn],dfn[maxn];
vector<int> ve[maxn];
struct node{
	int u,v;
}cut[maxn];
int tot,cnt;

int n,m,q;
int num[maxn];
int color[maxn],cn;
bool ins[maxn];
stack<int> s;
int in[maxn],out[maxn];
void init()
{
	memset(low,0,sizeof(low));
	memset(dfn,0,sizeof(dfn));
	cn=tot=cnt=0;
	for(int i=0;i<=n;i++){
		ve[i].clear();
		ins[i]=color[i]=0;
		num[i]=in[i]=out[i]=0;
	}
	while(!s.empty()) s.pop();
}
void tarjan(int u,int p)
{
	low[u]=dfn[u]=++cnt;
	s.push(u);
	ins[u]=1;
	for(int i=0;i<ve[u].size();i++){
		int v=ve[u][i];
		if(!dfn[v]){
			tarjan(v,u);
			low[u]=min(low[u],low[v]);
			if(low[v]>dfn[u]){
				cut[tot].u=u;
				cut[tot].v=v;
				tot++;
			}
		}else if(ins[v]){
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(low[u]==dfn[u]){
		cn++;
		while(s.top()!=u){
			color[s.top()]=cn;
			ins[s.top()]=0;
			s.pop();
		}
		color[u]=cn;
		ins[u]=0;
		s.pop();
		
	}
}

int main()
{
	int t,cas=1;
	scanf("%d",&t);
	while(t--){
		scanf("%d%d",&n,&m);
		init(); 
		int u,v;
		for(int i=0;i<m;i++){
			scanf("%d%d",&u,&v);
			ve[u].push_back(v);
			//ve[v].push_back(u);
		}
		for(int i=1;i<=n;i++)
			if(!dfn[i]) tarjan(i,0);

		for(int i=1;i<=n;i++) num[color[i]]++;
		printf("Case %d: ",cas++);
		if(cn==1){
			//printf("SD\n");//
			printf("-1\n");
			continue;
		}
		//缩点 建图 
//		for(int i=0;i
//			u=cut[i].u;
//			v=cut[i].v;
//			u=color[u];
//			v=color[v];
//			in[v]++;out[u]++;
//		}//不可用上述方法,因为我们这里建的是图,不是割边树 
		for(int u=1;u<=n;u++){
			for(int i=0;i<ve[u].size();i++){
				v=ve[u][i];
				if(color[u]!=color[v]){
					out[color[u]]++;
					in[color[v]]++; 
				}
			}
		} 
		ll ans=1LL*n*(n-1)-m,res=n;
		for(int v=1;v<=cn;v++){
			if(!in[v]||!out[v])
				res=min(res,1LL*num[v]);
		}
		printf("%lld\n",ans-res*(n-res));
	}
	return 0;
}

Caocao’s Bridges

HDU - 4738
题意:给定一个无向图,每个边有边权(守护人数),把这个图的一个桥(割边)炸掉,使得图不连通,要求需要人数不少于边权,求最少需要多少人。
题意:找出所有割边,求其中最小的割边做为答案。
注意:图本身不联通,答案为0;不存在桥,为-1;桥权值为0,需要一人。

#include
#include
#include
#include
#include
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=1004;
const int maxm=1000004;

int n,m;
struct Edge
{
	int v,next,w;
	bool flag;
}e[maxm<<1];
int head[maxn],tot,cnt;
void addedge(int u,int v,int w)
{
	e[tot].v=v;e[tot].flag=0;
	e[tot].next=head[u];
	e[tot].w=w;
	head[u]=tot++;
}
int dfn[maxn],low[maxn],ans;
int color[maxn],cn;
bool ins[maxn];
void tarjan(int u)
{
	low[u]=dfn[u]=++cnt;
	ins[u]=1;
	for(int i=head[u];~i;i=e[i].next){
		int v=e[i].v;
		if(e[i].flag) continue;
		e[i].flag=e[i^1].flag=1;//注意有重边不同权值的情况 ,不能用v!=p	
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
			if(dfn[u]<low[v]){
				ans=min(ans,e[i].w);
			}
		}else{//用v!=p过不了,是因为多重边的关系 
			low[u]=min(low[u],dfn[v]);
		}
	}
}
void init()
{
	memset(low,0,sizeof(low));
	memset(dfn,0,sizeof(dfn));
	memset(ins,0,sizeof(ins));
	memset(head,-1,sizeof(head));
	tot=cnt=cn=0;
}

int main()
{
	int x,y,w;
	while(~scanf("%d%d",&n,&m)){
		if(!n&&!m) break;
		init();
		while(m--){
			scanf("%d%d%d",&x,&y,&w);
			addedge(x,y,w);addedge(y,x,w);
		}
		ans=inf;
		int k=0;
		for(int i=1;i<=n;i++)
			if(!dfn[i]) tarjan(i),k++;
		if(k>1) printf("0\n"); 
		else if(ans==inf) printf("-1\n");
		else if(ans==0) printf("1\n");// 无人守,至少要一人 
		else printf("%d\n",ans);
	} 
	return 0;
}

Prince and Princess

HDU - 4685
题意:给n个王子,m个公主,每个王子匹配多个公主,求在满足最大匹配的情况下,每个王子能选择的公主数即具体的公主。
题解:求出最大匹配后,设最大匹配数为couple,两边分别建立(n-couple)个虚拟公主和(m-couple)虚拟王子。然后把每个王子匹配上的公主a,向和该王子能匹配的公主bi连边,那么最后能和被匹配上的公主a在同一个强连通分量的公主bi,则表示a能bi替换。
因为对于同一个连通分量的公主,就相当于她们移位一下她们的匹配王子,能保证最后的匹配数保持最大不变。
对于要建立虚拟公主、虚拟王子的原因,是为了解决如下样例的问题。
输入:
2 2
1 1
1 1
输出:
1 1
1 1
即王子a,b,公主c,d。a、b都能匹配c,但都不能匹配d,如果没有虚拟王子、虚拟公主来凑成完美匹配。那么答案(举例)可能就只有a匹配c,b不能匹配任何公主;这显然是不对的。

还有,对于这道题,用匈牙利算法求解最大匹配时,如果是王子向公主匹配,即为每个王子找公主的写法,则可以AC;如果是公主向王子匹配,即为每个公主找王子的写法,则会TLE,原因应该是王子向公主的边比较多,(题目说了ki<=500),这样匹配的dfs次数会比较多,时间复杂度高。

总结:对于二分匹配,一对多的情况下,跑算法时应该是为少的一方找对象,这样求解时间短一些。

#include
#include
#include
#include
#include
#include
using namespace std;
#define ll long long
const int maxn=1010;


int low[maxn],dfn[maxn];
vector<int> ve[maxn];
int cnt;

int n,m,q;
int color[maxn],cn;
bool ins[maxn];
stack<int> s;

int ly[maxn];
int lx[maxn];
bool vis[maxn],mp[maxn][maxn];
void init()
{
	memset(low,0,sizeof(low));
	memset(dfn,0,sizeof(dfn));
	cn=cnt=0;
	for(int i=0;i<=n+m;i++){
		ve[i].clear();
		ins[i]=color[i]=0;
	}
	while(!s.empty()) s.pop();
	memset(mp,0,sizeof(mp));
	memset(ly,-1,sizeof(ly));
}

bool dfs(int u)
{
	for(int v=1;v<=m;v++){
		if(mp[u][v]&&!vis[v]){
			vis[v]=1;
			if(ly[v]==-1||dfs(ly[v])){
				lx[u]=v;
				ly[v]=u;
				return 1;
			}
		}
	}
	return 0;
}
int maxMatch()
{
	int ans=0;
	for(int i=1;i<=n;i++){
		memset(vis,0,sizeof(vis));
		if(dfs(i)) ans++;
	}
	return ans;
}


void tarjan(int u)
{
	low[u]=dfn[u]=++cnt;
	s.push(u);
	ins[u]=1;
	for(int i=0;i<ve[u].size();i++){
		int v=ve[u][i];
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}else if(ins[v]){
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(low[u]==dfn[u]){
		cn++;
		while(s.top()!=u){
			color[s.top()]=cn;
			ins[s.top()]=0;
			s.pop();
		}
		color[u]=cn;
		ins[u]=0;
		s.pop();
		
	}
}
int main()
{
	int t,cas=1;
	scanf("%d",&t);
	while(t--){
		scanf("%d%d",&n,&m);
		init();
		int u,v,k;
		for(int u=1;u<=n;u++){
			scanf("%d",&k);
			while(k--){
				scanf("%d",&v);
				mp[u][v]=1;
			}
		}
		int couple=maxMatch();

		int N=n+m-couple;
		for(int i=n+1;i<=N;i++){
			for(int j=1;j<=N;j++){
				mp[i][j]=1;
			}
		}
		for(int i=m+1;i<=N;i++){
			for(int j=1;j<=N;j++){
				mp[j][i]=1;
			}
		}
		int n1=n,m1=m;
		n=m=N;
		memset(ly,-1,sizeof(ly));//---------
		memset(lx,-1,sizeof(lx));
		couple=maxMatch();

		
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++){
				if(mp[i][j]&&lx[i]!=j){
					ve[lx[i]].push_back(j);
				}
			}
		}
		
		for(int i=1;i<=n;i++)	
			if(!dfn[i]) tarjan(i);
		n=n1;m=m1;
		vector<int> cur;
		printf("Case #%d:\n",cas++);
		for(int i=1;i<=n;i++){
			cur.clear();
			for(int j=1;j<=m;j++){
				if(mp[i][j]&&color[lx[i]]==color[j])//mp[j][i]&&
					cur.push_back(j);
			}
			printf("%d",(int)cur.size());
			for(int j=0;j<cur.size();j++)
				printf(" %d",cur[j]);
			printf("\n");
		}
	}
	return 0;
}

你可能感兴趣的:(个人训练)