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