挑战程序设计竞赛 3.5 借助水流解决问题的网络流
【Summarize】
1. 二分图最大匹配=V-最大独立子集=最小点覆盖
2. 最小割的残余网络就是最大权闭合图点集
3. 当完成顺序与答案相关时可以考虑按时间拆点
4. 二分图匹配中出现障碍物等价于横纵另起一行
POJ 3713:Transferring Sylla(Tarjan)
/* 题目大意:给出一个图判断是不是三连通图,三连通图的意思是对于图中任意两点, 至少有三条路是可以相互连通的。 题解,我们可以枚举一个点去掉,然后判断图中是否存在割点,如果存在, 则说明这不是个三连通分图,否则则说明是三连通分图。 */ #include <cstdio> #include <vector> #include <algorithm> #include <cstring> using namespace std; const int MAX_N=510; vector<int> G[MAX_N]; int a,n,low[MAX_N],dep[MAX_N],col[MAX_N],m,root,b; bool flag=0; int dfs(int u,int fa,int t){ col[u]=1; dep[u]=low[u]=t; int tol=0,i,v; for(int i=0;i<G[u].size();i++){ v=G[u][i]; if(col[v]==2)continue; if(col[v]==0){ dfs(v,u,t+1); tol++; low[u]=min(low[u],low[v]); if(u==root&&tol>1||u!=root&&low[v]>=dep[u]){ flag=1; } }else if(col[v]==1&&v!=fa){ low[u]=min(low[u],dep[v]); } }return 0; } int solve(){ flag=0; for(int i=0;i<n;i++)G[i].clear(); for(int i=1;i<=m;i++){ scanf("%d%d",&a,&b); G[a].push_back(b); G[b].push_back(a); } for(int i=0;i<n;i++){ memset(col,0,sizeof(col)); memset(dep,0,sizeof(dep)); memset(low,0,sizeof(low)); col[i]=2; root=0; if(i==0)root=1; dfs(root,-1,1); for(int j=0;j<n;j++){ if(col[j]==0){flag=1;break;} }if(flag==1)break; }if(flag)puts("NO"); else puts("YES"); } int main(){ while(~scanf("%d%d",&n,&m),n+m)solve(); return 0; }
POJ 2914:Minimum Cut(全局最小割)
/* 题目大意:求出一个最小边割集,使得图不连通 题解:利用stoerwagner算法直接求出全局最小割,即答案。 */ #include <cstdio> #include <algorithm> #include <cstring> using namespace std; const int INF=0x3f3f3f3f; const int MAX_N=510; int G[MAX_N][MAX_N],v[MAX_N],f[MAX_N],N,M,vis[MAX_N]; int Stoer_Wagner(){ int ret=INF; for(int i=1;i<=N;i++)v[i]=i; while(N>1){ int k,lst=v[1]; memset(vis,0,sizeof(vis)); memset(f,0,sizeof(f)); vis[v[1]]=1; for(int i=2;i<=N;i++){ k=1; for(int j=2;j<=N;j++)if(!vis[v[j]]&&(f[v[j]]+=G[lst][v[j]])>f[v[k]])k=j; vis[v[k]]=1; if(i<N)lst=v[k]; }ret=min(ret,f[v[k]]); for(int j=1;j<=N;j++)G[v[j]][lst]=G[lst][v[j]]=G[lst][v[j]]+G[v[k]][v[j]]; v[k]=v[N--]; }return ret; } void init(){ memset(G,0,sizeof(G)); for(int i=1;i<=M;i++){ int x,y,z; scanf("%d%d%d",&x,&y,&z); ++x;++y; G[x][y]+=z;G[y][x]+=z; } } void solve(){printf("%d\n",Stoer_Wagner());} int main(){ while(~scanf("%d%d",&N,&M)){ init(); solve(); }return 0; }
POJ 2987:Firing(最大权闭合图)
/* 题目大意:为了使得公司效率最高,因此需要进行裁员, 裁去不同的人员有不同的效率提升效果,当然也有可能是负的效果, 如果裁去一个上级,那么他所管辖的下级需要全部裁掉,问最大效率提升 同时求出最小裁员 题解:我们从上司向所有的下属连线会发现裁去的部分恰是一个最大权闭合图 所以按照最大权闭合图建图求最小割,残余网络就是裁员数量。 */ #include <cstdio> #include <cstring> #include <vector> #include <queue> using namespace std; const int INF=0x3f3f3f3f; const int MAX_V=5010; typedef long long LL; struct edge{int to;LL cap;int rev;}; vector<edge> G[MAX_V]; int V,level[MAX_V],iter[MAX_V]; void add_edge(int from,int to,int cap){ G[from].push_back((edge){to,cap,G[to].size()}); G[to].push_back((edge){from,0,G[from].size()-1}); } void bfs(int s){ memset(level,-1,sizeof(level)); queue<int> que; level[s]=0; que.push(s); while(!que.empty()){ int v=que.front(); que.pop(); for(int i=0;i<G[v].size();i++){ edge &e=G[v][i]; if(e.cap>0&&level[e.to]<0){ level[e.to]=level[v]+1; que.push(e.to); } } } } LL dfs(int v,int t,LL f){ if(v==t)return f; for(int &i=iter[v];i<G[v].size();i++){ edge &e=G[v][i]; if(e.cap>0&&level[v]<level[e.to]){ LL d=dfs(e.to,t,min(f,e.cap)); if(d>0){ e.cap-=d; G[e.to][e.rev].cap+=d; return d; } } }return 0; } LL max_flow(int s,int t){ LL flow=0; for(;;){ bfs(s); if(level[t]<0)return flow; memset(iter,0,sizeof(iter)); LL f; while((f=dfs(s,t,INF))>0){ flow+=f; } } } const int MAX_N=5000; const int MAX_M=60000; int N,M,w[MAX_N],a[MAX_M],b[MAX_M]; LL max_weight_closure(int s,int t){ LL W=0; V=t; for(int i=0;i<=V;i++)G[i].clear(); for(int i=0;i<N;i++){ if(w[i]>0)W+=w[i],add_edge(s,i,w[i]); if(w[i]<0)add_edge(i,t,-w[i]); } for(int i=0;i<M;i++){ add_edge(a[i]-1,b[i]-1,INF); } return W-max_flow(s,t); } int leftv,vis[MAX_V]; void cal_res_net(int v){ ++leftv; vis[v]=1; for(int i=0;i<G[v].size();i++){ edge &e=G[v][i]; if(e.cap>0&&!vis[e.to])cal_res_net(e.to); } } void init(){ for(int i=0;i<N;i++)scanf("%d",&w[i]); for(int i=0;i<M;i++)scanf("%d%d",&a[i],&b[i]); } void solve(){ int s=N,t=N+1; LL max_profit=max_weight_closure(s,t); memset(vis,0,sizeof(vis)); leftv=0; cal_res_net(s); printf("%d %lld\n",--leftv,max_profit); } int main(){ while(~scanf("%d%d",&N,&M)){ init(); solve(); }return 0; }
POJ 3155:Hard Life(最大密度子图)
/* 题目大意:公司内部共n个员工,员工之间可能两两合不来。 若员工u和员工v有矛盾,用边(u, v)表示,共m个矛盾。 突然大股东送来一个富二代,威胁到你的CEO宝座。 你想分配给富二代一个垃圾团队,使得团队成员间的不团结率最高。 不团结率定义为团队人员间的矛盾总数与被裁人员数的比值 (不团结率=团队人员之间的矛盾总数/团队人员数)。 题解:原图即为求最大密度子图,利用分数规划和最大权闭合图求解。 */ #include <cstdio> #include <cstring> #include <vector> #include <queue> using namespace std; const double INF=0x3fffffff; const double eps=1e-8; const int MAX_V=110; typedef double cap_type; struct edge{ int to,rev; cap_type cap; edge(int to,cap_type cap,int rev):to(to),cap(cap),rev(rev){} }; vector<edge> G[MAX_V]; int V,level[MAX_V],iter[MAX_V]; void add_edge(int from,int to,cap_type cap){ G[from].push_back((edge){to,cap,G[to].size()}); G[to].push_back((edge){from,0,G[from].size()-1}); } void bfs(int s){ memset(level,-1,sizeof(level)); queue<int> que; level[s]=0; que.push(s); while(!que.empty()){ int v=que.front(); que.pop(); for(int i=0;i<G[v].size();i++){ edge &e=G[v][i]; if(e.cap>0&&level[e.to]<0){ level[e.to]=level[v]+1; que.push(e.to); } } } } cap_type dfs(int v,int t,cap_type f){ if(v==t)return f; for(int &i=iter[v];i<G[v].size();i++){ edge &e=G[v][i]; if(e.cap>0&&level[v]<level[e.to]){ cap_type d=dfs(e.to,t,min(f,e.cap)); if(d>0){ e.cap-=d; G[e.to][e.rev].cap+=d; return d; } } }return 0; } cap_type max_flow(int s,int t){ cap_type flow=0; for(;;){ bfs(s); if(level[t]<0)return flow; memset(iter,0,sizeof(iter)); cap_type f; while((f=dfs(s,t,INF))>0){ flow+=f; } } } const int MAX_M=1010; const int MAX_N=100; int N,M,x[MAX_M],y[MAX_M],D[MAX_N]; void construct_graph(int s,int t,cap_type g){ for(int i=0;i<MAX_V;i++)G[i].clear(); for(int i=0;i<N;i++){ add_edge(s,i,M); add_edge(i,t,M+2*g-D[i]); } for(int i=0;i<M;i++){ add_edge(x[i]-1,y[i]-1,1.0); add_edge(y[i]-1,x[i]-1,1.0); } } int leftv,vis[MAX_V]; void cal_res_net(int v){ ++leftv; vis[v]=1; for(int i=0;i<G[v].size();i++){ edge &e=G[v][i]; if(e.cap>eps&&!vis[e.to])cal_res_net(e.to); } } void init(){ memset(D,0,sizeof(D)); for(int i=0;i<M;i++){ scanf("%d%d",&x[i],&y[i]); D[x[i]-1]++; D[y[i]-1]++; } } void solve(){ if(M==0){printf("%d\n%d\n",1,1);return;} int s=N,t=N+1; double l=0,r=M,mid,tmp; const double Limit=1.0/N/N; while(r-l>=Limit){ mid=(l+r)/2; construct_graph(s,t,mid); tmp=(N*M-max_flow(s,t))/2; (tmp>eps?l:r)=mid; }construct_graph(s,t,l); max_flow(s,t); leftv=0; cal_res_net(s); printf("%d\n",leftv-1); for(int i=0;i<N;i++)if(vis[i])printf("%d\n",i+1); } int main(){ while(~scanf("%d%d",&N,&M)){ init(); solve(); }return 0; }
POJ 1274:The Perfect Stall
/* 题目大意:给出一些奶牛和他们喜欢的草棚,一个草棚只能待一只奶牛, 问最多可以满足几头奶牛 题解:奶牛和喜欢的草棚连线,做二分图匹配即可 */ #include <cstdio> #include <algorithm> #include <cstring> #include <vector> using namespace std; const int MAX_V=1000; int V,match[MAX_V]; vector<int> G[MAX_V]; bool used[MAX_V]; void add_edge(int u,int v){ G[u].push_back(v); G[v].push_back(u); } bool dfs(int v){ used[v]=1; for(int i=0;i<G[v].size();i++){ int u=G[v][i],w=match[u]; if(w<0||!used[w]&&dfs(w)){ match[v]=u; match[u]=v; return 1; } }return 0; } int bipartite_matching(){ int res=0; memset(match,-1,sizeof(match)); for(int v=0;v<V;v++){ if(match[v]<0){ memset(used,0,sizeof(used)); if(dfs(v))res++; } }return res; } int N,M,k,x; void solve(){ V=N+M; for(int i=0;i<V;i++)G[i].clear(); for(int i=0;i<N;i++){ scanf("%d",&k); for(int j=0;j<k;j++){ scanf("%d",&x); add_edge(i,N+x-1); } }printf("%d\n",bipartite_matching()); } int main(){ while(~scanf("%d%d",&N,&M)){ solve(); }return 0; }
POJ 2112:Optimal Milking
/* 题目大意:给出一些挤奶器,每台只能供给M头牛用,牛和挤奶器之间有一定的距离 现在要让每头牛都挤奶,同时最小化牛到挤奶器的距离,求最小距离 题解:首先用floyd计算出牛和挤奶器之间的距离, 我们二分最小答案,然后利用二分图检验是否可行,对于M匹配这个条件 可以将挤奶器拆点。 */ #include <cstdio> #include <algorithm> #include <cstring> #include <vector> using namespace std; const int MAX_V=1000; const int INF=0x3f3f3f3f; int V,match[MAX_V]; vector<int> G[MAX_V]; bool used[MAX_V]; void add_edge(int u,int v){ G[u].push_back(v); G[v].push_back(u); } bool dfs(int v){ used[v]=1; for(int i=0;i<G[v].size();i++){ int u=G[v][i],w=match[u]; if(w<0||!used[w]&&dfs(w)){ match[v]=u; match[u]=v; return 1; } }return 0; } int bipartite_matching(){ int res=0; memset(match,-1,sizeof(match)); for(int v=0;v<V;v++){ if(match[v]<0){ memset(used,0,sizeof(used)); if(dfs(v))res++; } }return res; } const int MAX_N=240; int K,C,M; int mp[MAX_N][MAX_N]; void construct_graph(int lim){ V=C+K*M; for(int i=0;i<=V;i++)G[i].clear(); for(int i=1;i<=C;i++){ for(int j=1;j<=K;j++){ if(mp[K+i][j]<=lim){ for(int t=1;t<=M;t++){ add_edge(i,C+(j-1)*M+t); } } } } } int init(){ for(int i=1;i<=K+C;i++) for(int j=1;j<=K+C;j++){ scanf("%d",&mp[i][j]); if(mp[i][j]==0)mp[i][j]=INF; } for(int k=1;k<=K+C;k++) for(int i=1;i<=K+C;i++) for(int j=1;j<=K+C;j++) mp[i][j]=min(mp[i][j],mp[i][k]+mp[k][j]); } void solve(){ int ans=1,l=1,r=200*(K+C); while(l<=r){ int mid=(l+r)>>1; construct_graph(mid); if(bipartite_matching()==C)ans=mid,r=mid-1; else l=mid+1; }printf("%d\n",ans); } int main(){ while(~scanf("%d%d%d",&K,&C,&M)){ init(); solve(); }return 0; }
POJ 1486:Sorting Slides
/* 题目大意:给出每张幻灯片的上下左右坐标,每张幻灯片的页码一定标在这张幻灯片上, 现在问你有没有办法唯一鉴别出一些幻灯片 题解:我们先求一遍匹配,然后对于匹配的边进行删去后再匹配, 如果匹配数量发生变化,则说明这条边不是完美匹配, 测试每一条边之后,我们就能得到完美匹配的边,就是答案。 */ #include <cstdio> #include <cstring> using namespace std; const int MAXV=500; int n,nedges,G[MAXV][MAXV],use[MAXV],match[MAXV],edgs[MAXV][2],mark[MAXV]; bool dfs(int x){ for(int i=1;i<=n;i++){ if(use[i]==0&&G[x][i]){ use[i]=1; int j=match[i]; if(j==-1||dfs(j)){ match[i]=x; return 1; } } }return 0; } int bipartite_matching(){ int count=0; memset(match,-1,sizeof(match)); for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++)use[j]=0; if(dfs(i))count++; }return count; } int x,y,xmin[MAXV],xmax[MAXV],ymin[MAXV],ymax[MAXV],cas=0; int main(){ while(scanf("%d",&n) && n){ memset(G,0,sizeof(G)); memset(mark,1,sizeof(mark)); for(int i=1;i<=n;i++)scanf("%d%d%d%d",&xmin[i],&xmax[i],&ymin[i],&ymax[i]); for(int i=1;i<=n;i++){ scanf("%d%d",&x,&y); for(int j=1;j<=n;j++) if(x>=xmin[j]&&x<=xmax[j]&&y>=ymin[j]&&y<=ymax[j])G[i][j]=1; }printf("Heap %d\n",++cas); if(bipartite_matching()==n){ nedges=n; for(int i=1;i<=n;i++){ edgs[i][0]=i; edgs[i][1]=match[i]; } for(int i=1;i<=n;i++){ G[edgs[i][1]][edgs[i][0]]=0; if(bipartite_matching()==n){ mark[i]=0; nedges--; }G[edgs[i][1]][edgs[i][0]]=1; } }else puts("none"); if(nedges==0)puts("none"); else{ for(int i=1;i<=n;i++)if(mark[i])printf("(%c,%d) ",edgs[i][0]+'A'-1,edgs[i][1]); puts(""); }puts(""); }return 0; }
POJ 1466:Girls and Boys
/* 题目大意:给出一些人和他们所喜欢的人,两个人相互喜欢就能配成一对, 问最后没有配对的人的最少数量 题解:求最少数量,就是最多匹配的补集,因此做一遍二分图匹配即可。 */ #include <cstdio> #include <algorithm> #include <cstring> #include <vector> using namespace std; const int MAX_V=1000; const int INF=0x3f3f3f3f; int V,match[MAX_V]; vector<int> G[MAX_V]; bool used[MAX_V]; void add_edge(int u,int v){ G[u].push_back(v); G[v].push_back(u); } bool dfs(int v){ used[v]=1; for(int i=0;i<G[v].size();i++){ int u=G[v][i],w=match[u]; if(w<0||!used[w]&&dfs(w)){ match[v]=u; match[u]=v; return 1; } }return 0; } int bipartite_matching(){ int res=0; memset(match,-1,sizeof(match)); for(int v=0;v<V;v++){ if(match[v]<0){ memset(used,0,sizeof(used)); if(dfs(v))res++; } }return res; } int N,x,k,y; void init(){ V=N; for(int i=0;i<V;i++)G[i].clear(); for(int i=0;i<N;i++){ scanf("%d: (%d)",&x,&k); for(int i=0;i<k;i++){ scanf("%d",&y); add_edge(x,y); } } } void solve(){ printf("%d\n",N-bipartite_matching()); } int main(){ while(~scanf("%d",&N)){ init(); solve(); }return 0; }
POJ 3692:Kindergarten
/* 题目大意:男生相互之间都认识,女生相互之间也都认识, 一些男生和一些女生相互之间也认识,求找出最多的人参加派对, 他们相互之间都认识 题解:我们建认识图的反图,将相互之间不认识的连线, 那么问题转化为求这个图的最大独立集, 最大独立集答案为总点数减去最大匹配。 */ #include <cstdio> #include <cstring> using namespace std; const int MAXV=500; int n,G[MAXV][MAXV],use[MAXV],match[MAXV]; int g,b,m,x,y,cas=0; bool dfs(int x){ for(int i=1;i<=b;i++){ if(use[i]==0&&G[x][i]){ use[i]=1; int j=match[i]; if(j==-1||dfs(j)){ match[i]=x; return 1; } } }return 0; } int bipartite_matching(){ int count=0; memset(match,-1,sizeof(match)); for(int i=1;i<=g;i++){ for(int j=1;j<=b;j++)use[j]=0; if(dfs(i))count++; }return count; } void init(){ memset(G,1,sizeof(G)); for(int i=0;i<m;i++){ scanf("%d%d",&x,&y); G[x][y]=0; } } void solve(){ printf("%d\n",g+b-bipartite_matching()); } int main(){ while(scanf("%d%d%d",&g,&b,&m),g||b||m){ printf("Case %d: ",++cas); init(); solve(); }return 0; }
POJ 2724:Purifying Machine
/* 题目大意:给出一些01串,如果一个位置为*,则表示这个位置可以填0和1,产生不同的串 现在要匹配所有产生的串,你可以生成一些模式,生成的模式中最多可以有一个*, 表示可以同时匹配这个位置上的0或1,问你至少生成几个串,就能匹配所有的串 题解:我们先生成所有的串,我们对这些串进行相互比较,如果只有一位不同, 那么就连一条边,构图完成之后求这个图的最大独立集就是答案。 */ #include <cstdio> #include <algorithm> #include <cstring> #include <vector> using namespace std; const int MAX_V=2000; const int INF=0x3f3f3f3f; int V,match[MAX_V]; vector<int> G[MAX_V]; bool used[MAX_V]; void add_edge(int u,int v){ G[u].push_back(v); G[v].push_back(u); } bool dfs(int v){ used[v]=1; for(int i=0;i<G[v].size();i++){ int u=G[v][i],w=match[u]; if(w<0||!used[w]&&dfs(w)){ match[v]=u; match[u]=v; return 1; } }return 0; } int bipartite_matching(){ int res=0; memset(match,-1,sizeof(match)); for(int v=0;v<V;v++){ if(match[v]<0){ memset(used,0,sizeof(used)); if(dfs(v))res++; } }return res; } void clear(){for(int i=0;i<V;i++)G[i].clear();} const int MAX_N=30; int N,M; vector<int> op; char s[MAX_N]; void solve(){ op.clear(); while(M--){ scanf("%s",s); int t=0; for(int i=0;i<N;i++)if(s[i]=='1')t+=1<<(N-i-1); op.push_back(t); for(int i=0;i<N;i++)if(s[i]=='*')t+=1<<(N-i-1); op.push_back(t); }sort(op.begin(),op.end()); op.erase(unique(op.begin(),op.end()),op.end()); V=op.size(); clear(); for(int i=0;i<V;i++){ for(int j=i+1;j<V;j++){ int diff=op[i]^op[j]; if((diff&(-diff))==diff)add_edge(i,j); } }printf("%d\n",V-bipartite_matching()); } int main(){ while(scanf("%d%d",&N,&M),N&&M)solve(); return 0; }
POJ 2226:Muddy Fields
/* 题目大意:给出一张图,上面有泥和草地,有泥的地方需要用1*k的木板覆盖, 有草地的地方不希望被覆盖,问在此条件下需要的最少木板数目 题解:我们将四周都当成草地,将每块草地拆成横向点和纵向点 对于每一块泥地,我们将其向左和向上遇到的第一块草地连线, 对于这个图的最小点覆盖就是答案,而最小点覆盖等于二分图的最大匹配, 因此我们做一遍最大匹配即可 */ #include <cstdio> #include <algorithm> #include <cstring> #include <vector> #include <queue> using namespace std; const int MAX_V=10000; int V,match[MAX_V]; vector<int> G[MAX_V]; bool used[MAX_V]; void add_edge(int u,int v){ G[u].push_back(v); G[v].push_back(u); } bool dfs(int v){ used[v]=1; for(int i=0;i<G[v].size();i++){ int u=G[v][i],w=match[u]; if(w<0||!used[w]&&dfs(w)){ match[v]=u; match[u]=v; return 1; } }return 0; } int bipartite_matching(){ int res=0; memset(match,-1,sizeof(match)); for(int v=0;v<V;v++){ if(match[v]<0){ memset(used,0,sizeof(used)); if(dfs(v))res++; } }return res; } const int MAX_N=60; char mp[MAX_N][MAX_N]; int g[MAX_N][MAX_N][2]; int T,N,M; void init(){ memset(g,0,sizeof(g)); for(int i=0;i<N;i++){ scanf("%s",mp[i]); } } void solve(){ int mark=1,cnt=0; for(int i=0;i<N;i++){ for(int j=0;j<M;j++){ if(mp[i][j]=='.')g[i][j][0]=-1,cnt++; else g[i][j][0]=cnt,mark=1; }if(mark)cnt++; } for(int j=0;j<M;j++){ mark=0; for(int i=0;i<N;i++){ if(mp[i][j]=='.')cnt++; else g[i][j][1]=cnt,mark=1; }if(mark)cnt++; } for(int i=0;i<N;i++){ for(int j=0;j<M;j++)if(~g[i][j][0]){ add_edge(g[i][j][0],g[i][j][1]); } }V=cnt; printf("%d\n",bipartite_matching()); for(int i=0;i<=V;i++)G[i].clear(); } int main(){ while(~scanf("%d%d",&N,&M)){ init(); solve(); }return 0; }
AOJ 2251:Merry Christmas
/* 题目大意:给出一张图,现在有一些任务,要求在ti时刻送礼物到pi地点 问至少需要几个圣诞老人才能准时完成任务 题解:我们将送完一个礼物之后还能赶到下一个地点送礼物的两个点之间连线 只要求出这个图的最小点覆盖就是答案 */ #include <cstdio> #include <algorithm> #include <cstring> #include <vector> #include <queue> using namespace std; const int MAX_V=2000; int V,match[MAX_V]; vector<int> G[MAX_V]; bool used[MAX_V]; void add_edge(int u,int v){ G[u].push_back(v); G[v].push_back(u); } bool dfs(int v){ used[v]=1; for(int i=0;i<G[v].size();i++){ int u=G[v][i],w=match[u]; if(w<0||!used[w]&&dfs(w)){ match[v]=u; match[u]=v; return 1; } }return 0; } int bipartite_matching(){ int res=0; memset(match,-1,sizeof(match)); for(int v=0;v<V;v++){ if(match[v]<0){ memset(used,0,sizeof(used)); if(dfs(v))res++; } }return res; } void clear(){for(int i=0;i<V;i++)G[i].clear();} const int MAX_N=100; const int MAX_L=1000; const int INF=0x3f3f3f3f; int N,M,L,x,y,z; int d[MAX_N][MAX_N],p[MAX_L],t[MAX_L]; void init(){ V=2*L; clear(); memset(d,INF,sizeof(d)); for(int i=0;i<M;i++){ scanf("%d%d%d",&x,&y,&z); d[x][y]=d[y][x]=z; } for(int i=0;i<L;i++)scanf("%d%d",&p[i],&t[i]); for(int k=0;k<N;k++){ d[k][k]=0; for(int i=0;i<N;i++) for(int j=0;j<N;j++) d[i][j]=min(d[i][j],d[i][k]+d[k][j]); } } void solve(){ for(int i=0;i<L;i++) for(int j=0;j<L;j++){ if(i!=j&&t[i]+d[p[i]][p[j]]<=t[j])add_edge(i<<1,j<<1|1); } printf("%d\n",L-bipartite_matching()); } int main(){ while(~scanf("%d%d%d",&N,&M,&L),N){ init(); solve(); }return 0; }
POJ 3068:Shortest pair of paths
/* 题目大意:给出一张图,要把两个物品从起点运到终点,他们不能运同一条路过 每条路都有一定的费用,求最小费用 题解:题目等价于求两条无交叉最短路,可用流量为2的费用流求解 */ #include <cstdio> #include <algorithm> #include <cstring> #include <vector> using namespace std; const int INF=0x3f3f3f3f; struct edge{int to,cap,cost,rev;}; const int MAX_V=10000; int V,dist[MAX_V],prevv[MAX_V],preve[MAX_V]; vector<edge> G[MAX_V]; void add_edge(int from,int to,int cap,int cost){ G[from].push_back((edge){to,cap,cost,G[to].size()}); G[to].push_back((edge){from,0,-cost,G[from].size()-1}); } int min_cost_flow(int s,int t,int f){ int res=0; while(f>0){ fill(dist,dist+V,INF); dist[s]=0; bool update=1; while(update){ update=0; for(int v=0;v<V;v++){ if(dist[v]==INF)continue; for(int i=0;i<G[v].size();i++){ edge &e=G[v][i]; if(e.cap>0&&dist[e.to]>dist[v]+e.cost){ dist[e.to]=dist[v]+e.cost; prevv[e.to]=v; preve[e.to]=i; update=1; } } } } if(dist[t]==INF)return -1; int d=f; for(int v=t;v!=s;v=prevv[v]){ d=min(d,G[prevv[v]][preve[v]].cap); }f-=d; res+=d*dist[t]; for(int v=t;v!=s;v=prevv[v]){ edge &e=G[prevv[v]][preve[v]]; e.cap-=d; G[v][e.rev].cap+=d; } }return res; } void clear(){for(int i=0;i<V;i++)G[i].clear();} int N,M; int cas=0; void solve(){ int s=N,t=s+1; V=t+1; clear(); add_edge(s,0,2,0); add_edge(N-1,t,2,0); for(int i=0;i<M;i++){ int u,v,cost; scanf("%d%d%d",&u,&v,&cost); add_edge(u,v,1,cost); } int ans=min_cost_flow(s,t,2); printf("Instance #%d: ", ++cas); if(ans==-1)puts("Not possible"); else printf("%d\n",ans); } int main(){ while(~scanf("%d%d",&N,&M),N&&M){ solve(); }return 0; }
POJ 2195:Going Home
/* 题目大意:给出一张图,上面有n个人和n个房子,现在每个人都让其回到其中一个房子中, 每个房子只能待一个人,现在要求总的路程最短,求这个最短路程 题解:每个人往每个房子连一条边,流量为1,费用为路程,那么在满流情况下的费用 就是总路程的最小值。 */ #include <cstdio> #include <algorithm> #include <cstring> #include <vector> #include <utility> using namespace std; const int INF=0x3f3f3f3f; struct edge{int to,cap,cost,rev;}; const int MAX_V=10000; int V,dist[MAX_V],prevv[MAX_V],preve[MAX_V]; vector<edge> G[MAX_V]; void add_edge(int from,int to,int cap,int cost){ G[from].push_back((edge){to,cap,cost,G[to].size()}); G[to].push_back((edge){from,0,-cost,G[from].size()-1}); } int min_cost_flow(int s,int t,int f){ int res=0; while(f>0){ fill(dist,dist+V,INF); dist[s]=0; bool update=1; while(update){ update=0; for(int v=0;v<V;v++){ if(dist[v]==INF)continue; for(int i=0;i<G[v].size();i++){ edge &e=G[v][i]; if(e.cap>0&&dist[e.to]>dist[v]+e.cost){ dist[e.to]=dist[v]+e.cost; prevv[e.to]=v; preve[e.to]=i; update=1; } } } } if(dist[t]==INF)return -1; int d=f; for(int v=t;v!=s;v=prevv[v]){ d=min(d,G[prevv[v]][preve[v]].cap); }f-=d; res+=d*dist[t]; for(int v=t;v!=s;v=prevv[v]){ edge &e=G[prevv[v]][preve[v]]; e.cap-=d; G[v][e.rev].cap+=d; } }return res; } void clear(){for(int i=0;i<V;i++)G[i].clear();} int N,M; int dis(pair<int,int> a,pair<int,int> b){ return abs(a.first-b.first)+abs(a.second-b.second); } void solve(){ vector<pair<int, int> >P; vector<pair<int, int> >H; for(int i=0;i<N;i++){ getchar(); for(int j=0;j<M;j++){ switch(getchar()){ case 'm': P.push_back(make_pair(i,j)); break; case 'H': H.push_back(make_pair(i,j)); break; } } }int size=P.size(); int s=size+size,t=s+1; V=t+1; clear(); for(int i=0;i<size;i++){ add_edge(s,i,1,0); add_edge(i+size,t,1,0); for(int j=0;j<size;j++){ add_edge(i,size+j,1,dis(P[i],H[j])); } }printf("%d\n",min_cost_flow(s,t,size)); } int main(){ while(~scanf("%d%d",&N,&M),N&&M){ solve(); }return 0; }
POJ 3422:Kaka's Matrix Travels
/* 题目大意:给出一个矩阵,从左上角到右下角走K次,每次只能往下或者往右, 每次走过这个格子就把这里的数字变成0,问k次之后,最多可以获得的总和是多少。 题解:我们对这个图进行建图,拆点连边,费用为负点权, 之后得到满流的最小费用的相反数就是答案。 */ #include <cstdio> #include <algorithm> #include <cstring> #include <vector> using namespace std; const int INF=0x3f3f3f3f; struct edge{int to,cap,cost,rev;}; const int MAX_V=10000; int V,dist[MAX_V],prevv[MAX_V],preve[MAX_V]; vector<edge> G[MAX_V]; void add_edge(int from,int to,int cap,int cost){ G[from].push_back((edge){to,cap,cost,G[to].size()}); G[to].push_back((edge){from,0,-cost,G[from].size()-1}); } int min_cost_flow(int s,int t,int f){ int res=0; while(f>0){ fill(dist,dist+V,INF); dist[s]=0; bool update=1; while(update){ update=0; for(int v=0;v<V;v++){ if(dist[v]==INF)continue; for(int i=0;i<G[v].size();i++){ edge &e=G[v][i]; if(e.cap>0&&dist[e.to]>dist[v]+e.cost){ dist[e.to]=dist[v]+e.cost; prevv[e.to]=v; preve[e.to]=i; update=1; } } } } if(dist[t]==INF)return -1; int d=f; for(int v=t;v!=s;v=prevv[v]){ d=min(d,G[prevv[v]][preve[v]].cap); }f-=d; res+=d*dist[t]; for(int v=t;v!=s;v=prevv[v]){ edge &e=G[prevv[v]][preve[v]]; e.cap-=d; G[v][e.rev].cap+=d; } }return res; } const int MAX_N=200; int N,K,u; void solve(){ int s=0,t=N*N*2-1; V=t+1;for(int i=0;i<V;i++)G[i].clear(); for(int i=0;i<N;i++){ for(int j=0;j<N;j++){ scanf("%d",&u); int p=i*N+j; add_edge(p,N*N+p,1,-u); add_edge(p,N*N+p,INF,0); if(i+1<N)add_edge(N*N+p,(i+1)*N+j,INF,0); if(j+1<N)add_edge(N*N+p,i*N+j+1,INF,0); } }printf("%d\n",-min_cost_flow(s,t,K)); } int main(){ while(~scanf("%d%d",&N,&K)){ solve(); }return 0; }
AOJ 2266:Cache Strategy(至多K区间相交权值和最大)
/* 题目大意:有M个桶,N个球,球编号为1到N,每个球都有重量w_i。 给出一个长K的数列,数列由球的编号构成。开始的时候,桶都是空的。 接着我们从前往后从数列中取出一个数a_j,执行如下操作: 如果球a_j已经存在于某一个桶中,那么什么也不干,花费为0,继续。 如果任何桶中都没有a_j,那么找一个桶放a_j, 如果该桶里还有球,那么取出原来的球,将a_j放进去。 这种操作的花费是a_j的重量w_a_j,与桶以及原来的球没关系。 求最小花费? 题解:我们先假设我们只有一个桶,那么答案就是权值总和, 然后我们考虑一下如何节省开支,多出来的m-1个桶可以让我们保留一些序号 一直到下一个相同序号,就节省下了这个序号的费用, 那这样,我们就得到了这个桶在这个时间区段,被这个序号所占用的信息 我们将每个桶需要保留到下一个相同需要需要的时间区段整理成一个线段集合, 那我们只要求出这些线段在最多相交m-1次的情况下能够得到的最大权值和, 就是我们可以节省的最大开支了。 */ #include <cstdio> #include <algorithm> #include <cstring> #include <vector> #include <utility> using namespace std; const int INF=0x3f3f3f3f; typedef long long LL; struct edge{ int to,cap,cost,rev; edge(int to,int cap,int cost,int rev):to(to),cap(cap),cost(cost),rev(rev){} }; const int MAX_V=10010; int V,dist[MAX_V],prevv[MAX_V],preve[MAX_V]; vector<edge> G[MAX_V]; void add_edge(int from,int to,int cap,int cost){ G[from].push_back(edge(to,cap,cost,G[to].size())); G[to].push_back(edge(from,0,-cost,G[from].size()-1)); } int min_cost_flow(int s,int t,int f){ int res=0; while(f>0){ fill(dist,dist+V,INF); dist[s]=0; bool update=1; while(update){ update=0; for(int v=0;v<V;v++){ if(dist[v]==INF)continue; for(int i=0;i<G[v].size();i++){ edge &e=G[v][i]; if(e.cap>0&&dist[e.to]>dist[v]+e.cost){ dist[e.to]=dist[v]+e.cost; prevv[e.to]=v; preve[e.to]=i; update=1; } } } } if(dist[t]==INF)return -1; int d=f; for(int v=t;v!=s;v=prevv[v]){ d=min(d,G[prevv[v]][preve[v]].cap); }f-=d; res+=d*dist[t]; for(int v=t;v!=s;v=prevv[v]){ edge &e=G[prevv[v]][preve[v]]; e.cap-=d; G[v][e.rev].cap+=d; } }return res; } void clear(){for(int i=0;i<=V;i++)G[i].clear();} const int MAX_N=10010; int M,N,K,w[MAX_N],lst[MAX_N],a[MAX_N]; int solve(){ for(int i=1;i<=N;i++)scanf("%d",&w[i]); for(int i=1;i<=K;i++)scanf("%d",&a[i]); int cnt=unique(a+1,a+K+1)-(a+1); int res=0; memset(lst,0,sizeof(lst)); V=cnt+1; clear(); for(int i=1;i<=cnt;i++){ res+=w[a[i]]; if(lst[a[i]])add_edge(lst[a[i]],i-1,1,-w[a[i]]); lst[a[i]]=i; }for(int i=1;i<cnt;i++)add_edge(i,i+1,INF,0); printf("%d\n",res+min_cost_flow(1,cnt,M-1)); } int main(){ while(~scanf("%d%d%d",&M,&N,&K))solve(); return 0; }
AOJ 2230:How to Create a Good Game
/* 题目大意:给出一张图,从1到n的最长路不变的情况下, 还能在不同的点之间增加最长总和的路为多长。 题解:如果将原DAG权值取反,然后从最后一关连一条正权边到第一关, 权值是最短路(负权值最短路=传统意义上的最长路)的长度的话, 那么那些正圈中的负权边就是应该增加权值的边,具体应该加多少,就是正圈的权值。 新建源点汇点,对于所有顶点,如果入度>出度,从源点连一条边到它, 否则,从它连一条边到汇点,容量都是是度数差。 */ #include <cstdio> #include <algorithm> #include <cstring> #include <vector> using namespace std; const int INF=0x3f3f3f3f; struct edge{ int to,cap,cost,rev; edge(int to,int cap,int cost,int rev):to(to),cap(cap),cost(cost),rev(rev){} }; const int MAX_V=10010; int V,dist[MAX_V],prevv[MAX_V],preve[MAX_V]; vector<edge> G[MAX_V]; void add_edge(int from,int to,int cap,int cost){ G[from].push_back(edge(to,cap,cost,G[to].size())); G[to].push_back(edge(from,0,-cost,G[from].size()-1)); } int min_cost_flow(int s,int t,int f){ int res=0; while(f>0){ fill(dist,dist+V,INF); dist[s]=0; bool update=1; while(update){ update=0; for(int v=0;v<V;v++){ if(dist[v]==INF)continue; for(int i=0;i<G[v].size();i++){ edge &e=G[v][i]; if(e.cap>0&&dist[e.to]>dist[v]+e.cost){ dist[e.to]=dist[v]+e.cost; prevv[e.to]=v; preve[e.to]=i; update=1; } } } } if(dist[t]==INF)return -1; int d=f; for(int v=t;v!=s;v=prevv[v]){ d=min(d,G[prevv[v]][preve[v]].cap); }f-=d; res+=d*dist[t]; for(int v=t;v!=s;v=prevv[v]){ edge &e=G[prevv[v]][preve[v]]; e.cap-=d; G[v][e.rev].cap+=d; } }return res; } void clear(){for(int i=0;i<V;i++)G[i].clear();} const int MAX_N=120; int N,M; int in[MAX_N],out[MAX_N],tot; void solve(){ int s=N,t=N+1;V=t+1;tot=0; clear(); memset(in,0,sizeof(in)); memset(out,0,sizeof(out)); for(int i=0;i<M;i++){ int x,y,z; scanf("%d%d%d",&x,&y,&z); add_edge(x,y,INF,-z); out[x]++;in[y]++; tot+=z; }int max_f=0; for(int i=0;i<N;i++){ if(in[i]>out[i]){ add_edge(s,i,in[i]-out[i],0); max_f+=in[i]-out[i]; }else{ add_edge(i,t,out[i]-in[i],0); } }min_cost_flow(0,N-1,1); add_edge(N-1,0,INF,-dist[N-1]); printf("%d\n",min_cost_flow(s,t,max_f)-tot); } int main(){ while(~scanf("%d%d",&N,&M)){ solve(); }return 0; }
愿你出走半生,归来仍是少年