图论优题2
1.Doves and bombs
UVA - 10765
算法:割点,割边
注释:
- 给定一个无向图,求解每个点可以作为几个双连通分量的割点,注意对遇特殊点的判断,当祖先节点没有或者有一个儿子的时候,可以作为一个连通分量的割点;当祖先节点有两个或者两个以上儿子的时候只需要求解他被标记过多少次;当非祖先节点时,应该对标记的次数再加1。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#define N 10010
using namespace std;
struct cut_point{
int cut_num;
int no;
}p[N];
vector<int>G[N];
int pre[N],low[N],dfs_clock,n,m;
bool iscut[N];
void init(int n)
{
memset(pre,0,sizeof(pre));
memset(low,0,sizeof(low));
memset(iscut,0,sizeof(iscut));
memset(p,0,sizeof(p));
dfs_clock=0;
for(int i=0;i<=n;i++)
{
G[i].clear();
p[i].no=i;
}
}
int dfs(int u,int fa)
{
int lowu=low[u]=pre[u]=++dfs_clock;
int child=0;
for(int i=0;i<G[u].size();i++)
{
int v=G[u][i];
if(!pre[v])
{
child++;
int lowv=dfs(v,u);
lowu=min(lowu,lowv);
if(lowv>=pre[u])
{
iscut[u]=true;
//if(u==0)cout<<v<<endl;
p[u].cut_num++;
}
}
else if(pre[v]<pre[u]&&v!=fa)
lowu=min(lowu,pre[v]);
}
//注意对特殊点的判断
if((child==1||child==0)&&fa==-1)
{
iscut[u]=0;
p[u].cut_num=1;
}
else if(fa>=0)p[u].cut_num++;
low[u]=lowu;
return lowu;
}
bool cmp(const cut_point &a,const cut_point &b)
{
if(a.cut_num==b.cut_num)return a.no<b.no;
return a.cut_num>b.cut_num;
}
int main()
{
//freopen("1.txt","r",stdin);
//freopen("2.txt","w",stdout);
int u,v;
while(scanf("%d%d",&n,&m)==2&&(n+m))
{
init(n);
while(scanf("%d%d",&u,&v)==2&&u!=-1&&v!=-1)
{
G[u].push_back(v);
G[v].push_back(u);
}
for(int i=0;i<n;i++)
if(!pre[i])dfs(i,-1);
sort(p,p+n,cmp);
for(int i=0;i<m;i++)
printf("%d %d\n",p[i].no,p[i].cut_num);
printf("\n");
}
return 0;
}
2.Wedding
UVA - 11294
算法:2-SET
注释:
- 典型的2-SET问题,但是在抽象为图的时候,妻子在新娘一旁为真,丈夫在新娘一旁为假。再者注意对遇特殊点的判断,就是当新娘与其他人有通奸关系时,可以不进行处理,当新郎与其他人通奸关系时,应该出现两条限制,因为肯定在新娘对面。在初始化图时,应该将新娘为真,因为其他人都是以新娘为参照点的。
#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
#include<stack>
#define N 40
using namespace std;
struct TwoSET{
int n;
bool mark[N<<1];
stack<int>s;
vector<int>G[N<<1];
void init(int n)
{
this->n=n;
memset(mark,0,sizeof(mark));
for(int i=0;i<=2*n;i++)G[i].clear();
mark[0]=1;//新娘必须确定为真,因为其他的条件是已新娘为真为基础的,不用担心会错过下面点的扫描,后面的点也可会扫描回来
}
void add_clause(int x,int xval,int y,int yval)
{
x=x*2+xval;
y=y*2+yval;
G[x^1].push_back(y);
G[y^1].push_back(x);
//cout<<(x^1)<<" "<<y<<" "<<(y^1)<<" "<<x<<endl;
}
bool dfs(int u)
{
//cout<<u<<endl;
if(mark[u^1])return false;
if(mark[u])return true;
mark[u]=true;
s.push(u);
for(int i=0;i<G[u].size();i++)
if(!dfs(G[u][i]))return false;
return true;
}
bool solve()
{
for(int i=0;i<2*n;i+=2)
{
if(!mark[i]&&!mark[i+1])
{
while(!s.empty())s.pop();
//cout<<endl;
if(!dfs(i))
{
while(!s.empty())
{
mark[s.top()]=false;
s.pop();
}
//cout<<endl;
if(!dfs(i+1))return false;
}
}
}
return true;
}
};
TwoSET solver;
int main()
{
//freopen("2.txt","w",stdout);
int n,m;
char a[4],b[4];
int x,xval,y,yval;
while(scanf("%d%d",&n,&m)&&(n+m))
{
solver.init(n);
for(int i=0;i<m;i++)
{
scanf("%s%s",a,b);
x=a[0]-'0';
if(a[1]=='w')xval=0;
else if(a[1]=='h')xval=1;
else
{
x=x*10+a[1]-'0';
if(a[2]=='w')xval=0;
else if(a[2]=='h')xval=1;
}
y=b[0]-'0';
if(b[1]=='w')yval=0;
else if(b[1]=='h')yval=1;
else
{
y=y*10+b[1]-'0';
if(b[2]=='w')yval=0;
else if(b[2]=='h')yval=1;
}
//cout<<x<<" "<<xval<<" "<<y<<" "<<yval<<endl;
if((x==0&&xval==0)||(y==0&&yval==0))continue;//当出现新娘和新郎是应该注意一下,这属于特殊点,需要特殊处理
if((x==0&&xval==1)||(y==0&&yval==1))
{
solver.add_clause(x,xval,y,yval);
solver.add_clause(x,xval^1,y,yval^1);
}
else solver.add_clause(x,xval,y,yval);
}
if(solver.solve())
for(int i=1;i<n;i++)
{
if(solver.mark[2*i]==solver.mark[0])cout<<i<<"w";
else if(solver.mark[2*i+1]==solver.mark[0])cout<<i<<"h";
if(i!=n-1)cout<<" ";
}
else cout<<"bad luck";
cout<<endl;
}
return 0;
}
3.Ants
UVA - 141
算法:二分图的最优完全匹配,KM算法
注释:
- 黑球连白球,典型的时间二分图,再看数据量,考虑到算法时间复杂度可能不低。
- 假设两条线段a1-b1和a2-b2相交,那么dist(a1,b1)+dist(a2,b2)一定大于dist(a1,b2)+dist(a2,b1),及四边形的两条交线的和大于任意两条对边和,所以我们只要使得黑白球相连的线段最短就可以。直接套用KM算法,求二部图的最短完全匹配。
- 注意二部图的最优匹配当中,虽然时间复杂度是O(n^4),但实际的情况并不糟糕。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define N 110
#define INF 10000000
using namespace std;
struct point{
int x,y;
}write[N],black[N];
double lx[N],ly[N],w[N][N];
int lef[N],n;
bool s[N],t[N];
double dist(int i,int j)
{
return sqrt((write[j].x-black[i].x)*(write[j].x-black[i].x)+(write[j].y-black[i].y)*(write[j].y-black[i].y));
}
bool match(int i)
{
s[i]=1;
for(int j=1;j<=n;j++)
{
if(!w[i][j])continue;
if(fabs(lx[i]+ly[j]-w[i][j])<1e-8&&!t[j])
{
t[j]=1;
if(lef[j]==0||match(lef[j]))
{
lef[j]=i;
return true;
}
}
}
return false;
}
void update()
{
double a=INF;
for(int i=1;i<=n;i++)if(s[i])
for(int j=1;j<=n;j++)if(!t[j])
a=min(a,lx[i]+ly[j]-w[i][j]);
for(int i=1;i<=n;i++)
{
if(s[i])lx[i]-=a;
if(t[i])ly[i]+=a;
}
}
void KM()
{
for(int i=1;i<=n;i++)
{
lx[i]=-INF;
ly[i]=0;
lef[i]=0;
for(int j=1;j<=n;j++)
if(w[i][j]!=0)
lx[i]=max(lx[i],w[i][j]);
}
for(int i=1;i<=n;i++)
{
for(;;)
{
for(int j=1;j<=n;j++)
cout<<lx[j]<<" "<<ly[j]<<endl;
cout<<endl;
memset(s,0,sizeof(s));
memset(t,0,sizeof(t));
if(match(i))break; else update();
}
}
}
int main()
{
//freopen("1.in","r",stdin);
//freopen("2.out","w",stdout);
while(scanf("%d",&n)!=EOF)
{
for(int i=1;i<=n;i++)
scanf("%d%d",&write[i].x,&write[i].y);
for(int i=1;i<=n;i++)
scanf("%d%d",&black[i].x,&black[i].y);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
w[i][j]=-dist(i,j);
}
KM();
for(int i=1;i<=n;i++)
printf("%d\n",lef[i]);
printf("\n");
}
return 0;
}
4.Golden Tiger Claw
UVA - 11383
算法:KM算法
注释:
- 本题是KM算法的副产物,由于KM算法针对的是完全二部图,所以完全是用于矩阵,每个格子代表一条边,行和列对应的是点;KM算法中的顶标lx[i]+ly[j]>=w[i][j]刚好对应真本题w(i,j)<=row(i)+col(j),并且要求row(i)和col(j)尽量小,KM最终结果每个lx[i]+ly[j]=v[i][j](v对应的是匹配边),所以本题是一个KM的模板题。
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 510
using namespace std;
int n,lef[N],lx[N],ly[N],w[N][N];
bool s[N],t[N];
bool match(int i)
{
s[i]=1;
for(int j=1;j<=n;j++)
{
if(lx[i]+ly[j]==w[i][j]&&!t[j])
{
t[j]=1;
if(lef[j]==0||match(lef[j]))
{
lef[j]=i;
return true;
}
}
}
return false;
}
void update()
{
int a=(1<<30);
for(int i=1;i<=n;i++)if(s[i])
for(int j=1;j<=n;j++)if(!t[j])
a=min(a,lx[i]+ly[j]-w[i][j]);
for(int i=1;i<=n;i++)
{
if(s[i])lx[i]-=a;
if(t[i])ly[i]+=a;
}
}
void KM()
{
for(int i=1;i<=n;i++)
{
lx[i]=ly[i]=lef[i]=0;
for(int j=1;j<=n;j++)
lx[i]=max(lx[i],w[i][j]);
}
for(int i=1;i<=n;i++)
{
for(;;)
{
memset(s,0,sizeof(s));
memset(t,0,sizeof(t));
if(match(i))break;else update();
}
}
}
int main()
{
//freopen("1.in","r",stdin);
while(scanf("%d",&n)!=EOF&&n)
{
int tot=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%d",&w[i][j]);
KM();
for(int i=1;i<=n;i++)
{
printf("%d ",lx[i]);
tot+=lx[i];
}
printf("\n");
for(int i=1;i<=n;i++)
{
printf("%d ",ly[i]);
tot+=ly[i];
}
printf("\n");
printf("%d\n",tot);
}
return 0;
}
5.Ladies’ Choice
UVALive - 3989
算法:稳定婚姻问题,Gale-Shapley算法
注释:
- 稳定婚姻问题也是二部图的匹配问题,不过没有边权,X节点有对Y节点的优先顺序,Y节点有对X节点的优先顺序,要求每个人都有对应的配偶,而且任意两个男人u和女人v(两人最终结果不是配偶)对对方的喜欢程度不同时高于现有配偶。
- Gale-Shapley算法利用了贪心的思想,男人不断地求婚和女人不断拒绝,最终结果男人得到自己有可能得到最好的配偶,而女人因为匹配的顺序真能嫁给有可能嫁给的最差的丈夫。
#include<iostream>
#include<cstdio>
#include<queue>
#define N 1010
using namespace std;
int pref[N][N],order[N][N],nxt[N];
int future_hushand[N],future_wife[N];
queue<int>q;
void engage(int man,int woman)
{
int m=future_hushand[woman];
if(m)
{
future_wife[m]=0;
q.push(m);
}
future_wife[man]=woman;
future_hushand[woman]=man;
}
int main()
{
int _,n,x;
cin>>_;
while(_--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
scanf("%d",&pref[i][j]);
future_wife[i]=0;
nxt[i]=1;
q.push(i);
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
scanf("%d",&x);
order[i][x]=j;
}
future_hushand[i]=0;
}
while(!q.empty())
{
int man=q.front();q.pop();
int woman=pref[man][nxt[man]++];
if(!future_hushand[woman])
engage(man,woman);
else if(order[woman][man]<order[woman][future_hushand[woman]])
engage(man,woman);
else q.push(man);
}
while(!q.empty())q.pop();
for(int i=1;i<=n;i++)
printf("%d\n",future_wife[i]);
if(_)printf("\n");
}
return 0;
}
二部图匹配的三个常见模型
- 三种问题在思考时都首先考虑了贪心的思想,就是让对应最多的先选上,其实不然,应该查看数据量,根据题目要求建立适当的二部图匹配模型。
6.SAM I AM
UVA - 11419
算法:增广路算法,二分图的最小覆盖
注释:
- 二分图的最小覆盖,即选择尽量少的点,使得每条边至少有一个端点被选中。最小覆盖数=最大匹配数,证明略。
- 最小覆盖的寻找,从X中所有的未匹配点出发扩展匈牙利树(必须是未匹配边匹配边交替,最终以匹配边结束),标记树中所有的点,则X中的标记点和Y中的已标记点组成所求的最小覆盖。
- 本题建模:以每行为X节点,每列为Y节点,有目标的点建立行与列的边,求最少的子弹数,就是求最小的覆盖数,最少几个点可以覆盖所有的边
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 1010
using namespace std;
int lef[N],r,c,tot;
bool w[N][N],s[N],t[N],vis[N][N];
bool is_row[N],is_col[N],book_row[N],book_col[N];
void init()
{
tot=0;
memset(lef,0,sizeof(lef));
memset(w,0,sizeof(w));
memset(s,0,sizeof(s));
memset(t,0,sizeof(t));
memset(is_row,0,sizeof(is_row));
memset(is_col,0,sizeof(is_col));
memset(book_row,0,sizeof(book_row));
memset(book_col,0,sizeof(book_col));
//memset(vis,0,sizeof(vis));
}
bool match(int i)
{
for(int j=1;j<=c;j++)
{
if(w[i][j]&&!t[j])
{
t[j]=1;
if(lef[j]==0||match(lef[j]))
{
lef[j]=i;
s[i]=1;
return true;
}
}
}
return false;
}
void dfs(int i)
{
//cout<<i<<" ";
book_row[i]=1;
for(int j=1;j<=c;j++)
{
if(w[i][j]&&lef[j]&&lef[j]!=i&&book_col[j]==0)
{
//cout<<j<<" "<<lef[j]<<endl;
book_col[j]=1;
dfs(lef[j]);
}
}
}
int main()
{
freopen("1.in","r",stdin);
int x,y,n,cnt=1;
while(scanf("%d%d%d",&r,&c,&n)&&(r+c+n))
{
cnt++;
//if(cnt==23)cout<<r<<" "<<c<<" "<<n<<endl;
init();
for(int i=1;i<=n;i++)
{
scanf("%d%d",&x,&y);
//if(cnt==23)cout<<x<<" "<<y<<endl;
w[x][y]=1;
is_row[x]=1;
is_col[y]=1;
}
for(int i=1;i<=r;i++)
{
memset(t,0,sizeof(t));
if(match(i))tot++;
}
for(int i=1;i<=r;i++)
{
if(!is_row[i])continue;
if(!s[i])dfs(i);
}
cout<<tot<<" ";
for(int i=1;i<=r;i++)
if(!is_row[i])continue;
else if(!book_row[i])cout<<"r"<<i<<" ";
for(int i=1;i<=c;i++)
if(!is_col[i])continue;
else if(book_col[i])cout<<"c"<<i<<" ";
cout<<endl;
}
return 0;
}
7.Guardian of Decency
UVALive - 3415
算法:匈牙利算法,二分图的最大独立点集。
注释:
- 二分图的最大独立点集问题,即在图中找尽量多的点,是的任意两个点之间没有边,与最小覆盖点集正好是互补的。最小覆盖点集中,每个点恰好对应所有边,剩下的点就是都相互之间没有边的点。
- 本题建模,任意两个可能发生恋爱关系的人之间建立一条边,寻找最大独立点集。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define N 510
using namespace std;
struct people{
int h;
char music[110];
char sport[110];
}man[N],woman[N];
int n,mcnt,wcnt;
int w[N][N],lef[N],tot;
bool s[N],t[N];
void init()
{
mcnt=wcnt=tot=0;
memset(w,0,sizeof(w));
memset(lef,0,sizeof(lef));
}
bool judge(int u,int v)
{
if(abs(man[u].h-woman[v].h)>40)return 0;
if(strcmp(man[u].music,woman[v].music)!=0)return 0;
if(strcmp(man[u].sport,woman[v].sport)==0)return 0;
return 1;
}
bool match(int i)
{
for(int j=1;j<=wcnt;j++)
{
if(w[i][j]&&!t[j])
{
t[j]=1;
if(lef[j]==0||match(lef[j]))
{
lef[j]=i;
return true;
}
}
}
return false;
}
int main()
{
//freopen("1.in","r",stdin);
int T,height;
char gender,music[110],sport[110];
scanf("%d",&T);
while(T--)
{
init();
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d %c%s%s",&height,&gender,music,sport);
if(gender=='M')
{
man[++mcnt].h=height;
strcpy(man[mcnt].music,music);
strcpy(man[mcnt].sport,sport);
}
else if(gender=='F')
{
woman[++wcnt].h=height;
strcpy(woman[wcnt].music,music);
strcpy(woman[wcnt].sport,sport);
}
}
for(int i=1;i<=mcnt;i++)
{
for(int j=1;j<=wcnt;j++)
{
if(judge(i,j))w[i][j]=1;
//cout<<w[i][j]<<" ";
}
//cout<<endl;
}
for(int i=1;i<=mcnt;i++)
{
memset(t,0,sizeof(t));
if(match(i))tot++;
}
printf("%d\n",mcnt+wcnt-tot);
}
return 0;
}
Taxi Cab Scheme
UVALive - 3126
算法:匈牙利算法,DAG的最小路径覆盖
注释:
- DAG的最小路径覆盖,用尽量少的路径覆盖DAG图上的所有边,不同路径之间不能有公共点,单独的点也可以作为路径。DAG图中,将每个点i一分为二,放在X节点一个i,放在Y节点一个i’,如果i->j那么在i和j’之间连一条边。求二分图的最大匹配,用节点数减去最大匹配数对应结果。
- 分析本题,如果在接完一个人之后还能再接一个人,建立一条边。
- 还可以解决DAG的带权图,但是不能解决有环图。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#define M 510
using namespace std;
struct guest{
int time;
int sx,sy,ex,ey;
}g[M];
int m,w[M][M],lef[M],tot;
bool s[M],t[M];
void init()
{
tot=0;
memset(w,0,sizeof(w));
memset(lef,0,sizeof(lef));
}
bool is_DAG(int u,int v)
{
if(g[u].time+abs(g[u].ex-g[u].sx)+abs(g[u].ey-g[u].sy)+abs(g[u].ex-g[v].sx)+abs(g[u].ey-g[v].sy)<g[v].time)return 1;
else return 0;
}
bool match(int i)
{
for(int j=1;j<=m;j++)
{
if(w[i][j]&&!t[j])
{
t[j]=1;
if(lef[j]==0||match(lef[j]))
{
lef[j]=i;
return true;
}
}
}
return false;
}
int main()
{
freopen("1.in","r",stdin);
int T;
char ti[6];
scanf("%d",&T);
while(T--)
{
init();
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
scanf("%s%d%d%d%d",ti,&g[i].sx,&g[i].sy,&g[i].ex,&g[i].ey);
g[i].time=(((ti[0]-'0')*10+ti[1]-'0')*60+(ti[3]-'0')*10)+ti[4]-'0';
//cout<<g[i].time<<" "<<g[i].sx<<" "<<g[i].sy<<" "<<g[i].ex<<" "<<g[i].ey<<endl;
}
for(int i=1;i<=m;i++)
{
for(int j=i+1;j<=m;j++)
{
if(is_DAG(i,j))w[i][j]=1;
if(is_DAG(j,i))w[j][i]=1;
//if(w[i][j])cout<<i<<" "<<j<<endl;
//if(w[j][i])cout<<j<<" "<<i<<endl;
}
}
for(int i=1;i<=m;i++)
{
memset(t,0,sizeof(t));
if(match(i))tot++;
}
cout<<m-tot<<endl;
}
return 0;
}