给定一个 r*c 的格子矩阵,有C发子弹,每列格子有2个白色的方块,在1,2,3,...c 列上开枪打白色的格子,打到一个,那么这个白色方块所在的行就算打到了,求怎样打,才能把所有的行全部打到,如果全部打到而且有多余的列,那么这列随便打一个方块就可以了,按列的1,2,3,4......C的顺序,输出要打的行号。
思路:
解法一:(有源汇上下界可行流)
建图:
(1):定义一个源点s向每个行连一条[1,C]的边
(2):定义一个汇点t,每个列向t连一条[1,1]的边
(3):如果第i列的白色棋子在u,v行,则u,v行向i列连一条[0,1]的边
(4):t到s连一条[0,INF]的边,目的是将有源汇转化成无源汇
(5):建立附加源汇ss,tt;
(6):最后判断是否有可行解
PS.一开始MAXN开成2005狂WA,改成2006就AC了,害我查了两个小时,郁闷,以后开数组还是要留点余地的、、、、
解法二:(二分匹配)
(1):用R去匹配C,若匹配数(ans)不等于R则输出"NO";
(2):否则有解,输出答案时,那些没有匹配的列可以随便对应行输出
源代码(1):
/*有源汇上下界可行流*/ /*AC代码:32ms*/ #include <iostream> #include <cstdio> #include <memory.h> #include <algorithm> #define MAXN 2006//原本开2005,WA到死啊 #define INF 1e8 #define min(a,b) (a<b?a:b) #define max(a,b) (a>b?a:b) using namespace std; struct edge { int u,v,w,next; }E[2000000]; int head[MAXN],ecnt; int cur[MAXN],gap[MAXN],pre[MAXN],dis[MAXN]; int deg[MAXN]; int N,M,s,t,ss,tt; int ans[MAXN]; bool ok; void Insert(int u,int v,int w) { E[ecnt].u=u; E[ecnt].v=v; E[ecnt].w=w; E[ecnt].next=head[u]; head[u]=ecnt++; E[ecnt].u=v; E[ecnt].v=u; E[ecnt].w=0; E[ecnt].next=head[v]; head[v]=ecnt++; } int Sap(int s,int t,int n)//Sap(模版) { int ans=0,aug=INF;//aug表示增广路的流量 int i,v,u=pre[s]=s; for(i=0;i<=n;i++) { cur[i]=head[i]; dis[i]=gap[i]=0; } gap[s]=n; bool flag; while(dis[s]<n) { flag=false; for(int &j=cur[u];j!=-1;j=E[j].next) { v=E[j].v; if(E[j].w>0&&dis[u]==dis[v]+1) { flag=true;//找到容许边 aug=min(aug,E[j].w); pre[v]=u; u=v; if(u==t) { ans+=aug; while(u!=s) { u=pre[u]; E[cur[u]].w-=aug; E[cur[u]^1].w+=aug; } aug=INF; } break; } } if(flag) continue; int mindis=n; for(i=head[u];i!=-1;i=E[i].next) { v=E[i].v; if(E[i].w>0&&dis[v]<mindis) { mindis=dis[v]; cur[u]=i; } } if((--gap[dis[u]])==0) break; gap[dis[u]=mindis+1]++; u=pre[u]; } return ans; } //------------------------------------------------------// void Init() { int i,u,v; memset(head,-1,sizeof(head));ecnt=0; memset(deg,0,sizeof(deg)); ok=true; scanf("%d%d",&N,&M); if(N>M) ok=false;//N>M一定无解 s=0;t=N+M+1;ss=t+1;tt=ss+1; for(i=N+1;i<=N+M;i++) { scanf("%d%d",&u,&v); Insert(u,i,1); Insert(v,i,1); } if(!ok) return;//剪枝 for(i=1;i<=N;i++) { Insert(s,i,M-1); deg[s]--; deg[i]++; } for(i=N+1;i<=N+M;i++) { //Insert(i,t,0);//上下界都为1,不用添边了 deg[i]--; deg[t]++; } //先将有源汇转化成无源汇 Insert(t,s,INF); for(i=s;i<=t;i++) { if(deg[i]>0) Insert(ss,i,deg[i]); else if(deg[i]<0) Insert(i,tt,-deg[i]); } } void Solve() { int i,u,v; if(!ok) {printf("NO\n");return;} Sap(ss,tt,tt+1); for(i=head[ss];i!=-1;i=E[i].next) { if(E[i].w>0) {ok=false;break;} } if(!ok) printf("NO\n"); else { for(i=0;i<4*M;i+=2) { u=E[i].u;v=E[i].v; if(!E[i].w) {ans[v]=u;} } for(i=1+N;i<N+M;i++) printf("%d ",ans[i]); printf("%d\n",ans[i]); } } int main() { int T; scanf("%d",&T); while(T--) { Init(); Solve(); } return 0; }
源代码(2):
/*二分匹配*/ /*思路: (1):用R去匹配C,若匹配数(ans)不等于R则输出"NO"; (2):否则有解,输出答案时,那些没有匹配的列可以随便对应行输出 */ /*AC代码:32ms*/ #include <iostream> #include <cstdio> #include <algorithm> #include <memory.h> using namespace std; bool map[1005][1005]; bool flag[1005]; int result[1005]; int R,C; void Init() { int i,u,v; memset(map,false,sizeof(map)); memset(result,-1,sizeof(result)); scanf("%d%d",&R,&C); for(i=1;i<=C;i++) { scanf("%d%d",&u,&v); map[u][i]=map[v][i]=true; } } bool Find(int x) { int i; for(i=1;i<=C;i++) { if(map[x][i]&&!flag[i]) { flag[i]=true; if(result[i]==-1||Find(result[i])) { result[i]=x; return true; } } } return false; } void Solve() { int i,j,ans=0; for(i=1;i<=R;i++) { memset(flag,false,sizeof(flag)); if(Find(i)) ans++; } if(ans!=R) printf("NO\n"); else { for(i=1;i<=C;i++) { if(result[i]!=-1) printf("%d",result[i]); else { for(j=1;j<=R;j++) { if(map[j][i]) {printf("%d",j);break;} } } if(i!=C) printf(" "); } printf("\n"); } } int main() { int T; scanf("%d",&T); while(T--) { Init(); Solve(); } return 0; }