题目地址:ZOJ 3348
仍然是一道竞赛问题的网络流问题,但是这道题再用上次的竞赛建图方法就不行了,5000场比赛,明显会超时,于是需要换种建图思路了。上一道经典竞赛问题戳这里
上一道的胜负转换是利用专门给比赛建一个点,通过对比赛双方的流向来控制胜负关系,这里的建图方法更加巧妙(膜拜想出这个方法的大牛。。。),是先假设其中一方获胜,用mp[a][b]来表示a赢b的次数,将a与b连边,权值为mp[a][b],这样的话,前面的假设就仅仅只是假设而已,因为在这里,如果a的流量流向了b,说明a的胜利果实到了b,相当于b获胜。这是题解上的说法,但是我个人是理解的是在这里把比赛转换成了边(上一道是点),这条边连接比赛双方,然后这条边上的流量通过某一方从源点里加入都行,最后看从哪个端点流向了汇点就是相当于哪边赢,这是我个人的理解。
建图方法就是建一源点与汇点,每个人都与源点连边,权值为这个人将要获胜的场数,与汇点连边,权值为与DD的积分差-1,很明显是用来保证积分不超过DD的,然后将假设的胜负关系用有向边将各点连接起来。最后求一次最大流判断是否满流。对于每个人的转换处理,我是直接用的map映射,这样比较方便。
代码如下;
#include <iostream> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h> #include <ctype.h> #include <queue> #include <map> #include<algorithm> using namespace std; const int INF=0x3f3f3f3f; int head[200], source, sink, nv, cnt; int cur[200], num[200], d[200], pre[200], q[200], w[200], mp[200][200]; struct node { int u, v, cap, next; } edge[1000000]; void add(int u, int v, int cap) { edge[cnt].v=v; edge[cnt].cap=cap; edge[cnt].next=head[u]; head[u]=cnt++; edge[cnt].v=u; edge[cnt].cap=0; edge[cnt].next=head[v]; head[v]=cnt++; } void bfs() { memset(num,0,sizeof(num)); memset(d,-1,sizeof(d)); int f1=0, f2=0, i; d[sink]=0; num[0]=1; q[f1++]=sink; while(f1>=f2) { int u=q[f2++]; for(i=head[u]; i!=-1; i=edge[i].next) { int v=edge[i].v; if(d[v]==-1) { d[v]=d[u]+1; num[d[v]]++; q[f1++]=v; } } } } int isap() { memcpy(cur,head,sizeof(cur)); bfs(); int flow=0, u=pre[source]=source, i; while(d[source]<nv) { if(u==sink) { int f=INF,pos; for(i=source; i!=sink; i=edge[cur[i]].v) { if(f>edge[cur[i]].cap) { f=edge[cur[i]].cap; pos=i; } } for(i=source; i!=sink; i=edge[cur[i]].v) { edge[cur[i]].cap-=f; edge[cur[i]^1].cap+=f; } flow+=f; u=pos; } for(i=cur[u]; i!=-1; i=edge[i].next) { if(d[edge[i].v]+1==d[u]&&edge[i].cap) break; } if(i!=-1) { cur[u]=i; pre[edge[i].v]=u; u=edge[i].v; } else { if(--num[d[u]]==0) break; int mind=nv; for(i=head[u]; i!=-1; i=edge[i].next) { if(mind>d[edge[i].v]&&edge[i].cap) { mind=d[edge[i].v]; cur[u]=i; } } d[u]=mind+1; num[d[u]]++; u=pre[u]; } } return flow; } int main() { int n, m, p, i, j, z; char s1[20], s2[20], s3[20]; map<string,int>Q; while(scanf("%d%d",&n,&m)!=EOF) { memset(w,0,sizeof(w)); memset(head,-1,sizeof(head)); cnt=0; z=1; Q.clear(); char s4[20]="DD"; Q[s4]=1; for(i=0; i<m; i++) { scanf("%s%s%s",s1,s2,s3); if(!Q[s1]) Q[s1]=++z; if(!Q[s2]) Q[s2]=++z; if(strcmp(s3,"win")==0) { w[Q[s1]]++; } else { w[Q[s2]]++; } } scanf("%d",&p); memset(mp,0,sizeof(mp)); for(i=0; i<p; i++) { scanf("%s%s",s1,s2); if(!Q[s1]) Q[s1]=++z; if(!Q[s2]) Q[s2]=++z; if(Q[s1]==1||Q[s2]==1) { w[1]++; } else { mp[Q[s1]][Q[s2]]++; w[Q[s1]]++; } } int sum=0; for(i=2;i<=z;i++) sum+=w[i]; source=0; sink=z; nv=sink+1; for(i=2; i<=z; i++) { add(source,i-1,w[i]); add(i-1,sink,w[1]-1); for(j=2; j<=z; j++) { add(i-1,j-1,mp[i][j]); } } int x=isap(); //printf("%d\n",x); if(x==sum) printf("Yes\n"); else printf("No\n"); } return 0; }
ZOJ 3348 Schedule(map运用+网络流之最大流)(竞赛问题升级版)
原文地址:http://blog.csdn.net/scf0920/article/details/37958777