P3386 【模板】二分图匹配
#include<bits/stdc++.h> #define ll long long #define ri register int using namespace std; ll n,m,e,link[1005][1005],vis[2005],match[2005],ans; ll inline read(){ ll f=1,sum=0; char ch=getchar(); while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();} while(isdigit(ch)){sum=(sum<<1)+(sum<<3)+(ch^48);ch=getchar();} return f*sum; } bool dfs(ll x){ for(ri i=n+1;i<=n+m;i++){ if(link[x][i]&&!vis[i]){ vis[i]=1; if(!match[i]||dfs(match[i])){ match[i]=x; return true; } } } return false; } int main(){ n=read();m=read();e=read(); for(ri i=1;i<=e;i++){ ll x,y; x=read();y=read(); if(x>n||y>m)continue; link[x][n+y]=1; } for(ri i=1;i<=n;i++){ memset(vis,0,sizeof(vis)); if(dfs(i))ans++; } printf("%lld",ans); return 0; }
P1640 [SCOI2010]连续攻击游戏
比较特别的二分图,都能想到把两个属性分为左右端点,但难以解决这道题,于是换一个很妙的思路:把物品的两个属性与编号分别连边,即属性与边为左右端点。
#include<bits/stdc++.h> #define ri register int #define ll long long #define For(i,l,r) for(ri i=l;i<=r;i++) #define Dfor(i,r,l) for(ri i=r;i>=l;i--) using namespace std; const int M=1e6+6; int n,head[M],cnt,ans,pre[M]; struct node{ int nxt,to; }e[M<<1]; bool vis[M]; inline ll read(){ ll f=1,sum=0; char ch=getchar(); while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();} while(isdigit(ch)){sum=(sum<<1)+(sum<<3)+(ch^48);ch=getchar();} return f*sum; } inline void add(int u,int v){ e[++cnt].nxt=head[u]; head[u]=cnt; e[cnt].to=v; } inline bool dfs(int u){ if(vis[u]) return 0; vis[u]=1; for(ri i=head[u];i;i=e[i].nxt){ int v=e[i].to; if((!pre[v])||dfs(pre[v])){ pre[v]=u;return 1; } } return 0; } int main(){ n=read(); For(i,1,n){ int a=read(),b=read(); add(a,i),add(b,i); } For(i,1,n){ memset(vis,0,sizeof(vis)); if(dfs(i)) ans++; else break; } printf("%d\n",ans); return 0; }
P1129 [ZJOI2007]矩阵游戏
同样比较妙的一道二分图的题,假如i行j列(即a[i][j])为黑子,那么把j连到i(把i连到j一样),也就是把行列做左右端点,计算出最大匹配,如果==n那么就输出Yes否则No。
*另外就是二分图都差不多,基本只会改两个地方,要么是G[i][j]或e[i].u/v(邻接矩阵/链式前向星),看题目要求要哪个好写;要么就是vis[i]时直接退出还是跳过匹配过程。
#include<bits/stdc++.h> #define For(i,l,r) for(int i=l;i<=r;i++) using namespace std; const int N=505; int n,ans,t,pre[N]; bool G[N][N],vis[N]; inline int read(){ int f=1,sum=0; char ch=getchar(); while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();} while(isdigit(ch)){sum=(sum<<1)+(sum<<3)+(ch^48);ch=getchar();} return f*sum; } inline bool dfs(int x){ For(i,1,n){ if(G[x][i]&&!vis[i]){ vis[i]=1; if(!pre[i]||dfs(pre[i])){ pre[i]=x;return 1; } } } return 0; } int main(){ t=read(); while(t--){ memset(pre,0,sizeof(pre)); memset(G,0,sizeof(G)); ans=0;n=read(); For(i,1,n){ For(j,1,n){ G[i][j]=read(); } } For(i,1,n){ memset(vis,0,sizeof(vis)); ans+=dfs(i); } if(ans==n) printf("Yes\n"); else printf("No\n"); } return 0; }
P1963 [NOI2009]变换序列
如果真的理解了二分图的原理&过程&代码,思路还是很好想的,我之前就是吃了对每个,尤其是基础算法半懂不懂,应用也半熟不会自己完整打完一道题的苦,其实联赛范围内基本也就考这些了,省选不知。
思路就是一个倒着走的二分图,这样能保证字典序最小,我想到了但又同之前一万次一样觉得没有正确性,其实如果没能理解手推一下也能发现它是正确的,前提是推的认真正确。
然后要优先连小点的数字,正确性其实手推一下也能出来。
其实上述写法我都是感性理解觉得行,正确性不会证所以没打,又懒得手推。但事实证明这真的是赌博,还是必须要会自己证,实在实在不行再手推几组数据,考虑情况全面点保证正确。
#include<bits/stdc++.h> #define ri register int #define ll long long #define For(i,l,r) for(ri i=l;i<=r;i++) #define Dfor(i,r,l) for(ri i=r;i>=l;i--) using namespace std; const int M=1e4+4; int n,d[M],pre[M],G[M][3],to[M]; bool vis[M]; inline ll read(){ ll f=1,sum=0; char ch=getchar(); while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();} while(isdigit(ch)){sum=(sum<<1)+(sum<<3)+(ch^48);ch=getchar();} return f*sum; } inline bool dfs(int x){ For(i,0,1){ int v=G[x][i]; if(!vis[v]){ vis[v]=1; if((pre[v]==-1)||dfs(pre[v])){ pre[v]=x;to[x]=v;return 1; } } } return 0; } int main(){ n=read(); For(i,0,n-1) d[i]=read(); For(i,0,n-1){ G[i][0]=(i+d[i]+n)%n,G[i][1]=(i-d[i]+n)%n; if(G[i][0]>G[i][1]) swap(G[i][0],G[i][1]); pre[i]=-1; } Dfor(i,n-1,0){ memset(vis,0,sizeof(vis)); if(!dfs(i)) {printf("No Answer\n");return 0;} } For(i,0,n-1) printf("%d ",to[i]); return 0; }
扫描二维码关注公众号,回复:
7764502 查看本文章