匹配主要就是二分图匹配和一般图匹配,一般图的带花树算法我还不会
二分图匹配主要就是匈牙利算法,也可以用最大流解决
具体的看blog:http://www.renfei.org/blog/bipartite-matching.html 讲的比较清楚了
今天做了几个简单题
hdu 1045
题意:给你个棋盘,一部分地方不能放,然后问你最多放多少个,同一行同一列不能有多个,不过如果两个同行或者同列之间隔着一个墙,就能放
题解:这题就是建图难,一开始想不到吧,可以把没有分开的一段行和列缩成一个点,然后如果这段行和某点列有相交部分,就是这两个点之间有边,然后最多放多少个棋子,就是最多取多少条边,就是二分图的最大匹配问题了
#include <map> #include <set> #include <stack> #include <queue> #include <cmath> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <cstring> #include <sstream> #include <cstdlib> #include <iostream> #include <algorithm> #pragma comment(linker,"/STACK:102400000,102400000") using namespace std; #define MAX 1005 #define MAXN 2000005 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define lrt rt<<1 #define rrt rt<<1|1 #define mid int m=(r+l)>>1 #define LL long long #define ull unsigned long long #define mem0(x) memset(x,0,sizeof(x)) #define mem1(x) memset(x,-1,sizeof(x)) #define meminf(x) memset(x,INF,sizeof(x)) #define lowbit(x) (x&-x) const LL mod = 1000000; const int prime = 999983; const int INF = 0x3f3f3f3f; const int INFF = 1e9; const double pi = 3.141592653589793; const double inf = 1e18; const double eps = 1e-10; int edge[20][20]; int head[MAX]; int row[5][5]; int col[5][5]; int match[MAX]; int used[MAX]; int tot; int cnt; int n; void init(){ mem0(edge); mem1(match); mem0(col); mem0(row); tot=0; cnt=0; } bool dfs(int u){ used[u]=1; for(int i=1;i<=cnt;i++){ if(!edge[u][i]) continue; int w=match[i]; if(w<0||(!used[w]&&dfs(w))){ match[i]=u; match[u]=i; return true; } } return false; } int main(){ string s[5]; while(scanf("%d",&n)&&n){ init(); for(int i=0;i<n;i++) cin>>s[i]; for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ if(s[i][j]=='.'&&j!=0&&s[i][j-1]=='.') row[i][j]=cnt; else if(s[i][j]=='.') row[i][j]=++cnt; } } for(int j=0;j<n;j++){ for(int i=0;i<n;i++){ if(s[i][j]=='.'&&i!=0&&s[i-1][j]=='.') col[i][j]=cnt; else if(s[i][j]=='.') col[i][j]=++cnt; } } for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ if(s[i][j]=='.'){ edge[row[i][j]][col[i][j]]=edge[col[i][j]][row[i][j]]=1; } } } int ans=0; for(int i=1;i<=cnt;i++){ if(match[i]<0){ mem0(used); if(dfs(i)) ans++; } } printf("%d\n",ans); } return 0; }
题意:给你一些朋友关系,然后问把他们分成两组,每组中都不是朋友,第一组中每个人在第二组中都有一个朋友,明显就是二分图的最大匹配,不过就是可能给你的图有环,问你能不能构成二分图
题解:就是首先判断是否是二分图,这个用染色算法,就是dfs,如果搜到一条边上两端颜色相同就显然不是二分图。
判断完是二分图之后直接就匈牙利上了
数组貌似比较恶心,要开的大一点
#include <map> #include <set> #include <stack> #include <queue> #include <cmath> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <cstring> #include <sstream> #include <cstdlib> #include <iostream> #include <algorithm> #pragma comment(linker,"/STACK:102400000,102400000") using namespace std; #define MAX 1005 #define MAXN 2000005 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define lrt rt<<1 #define rrt rt<<1|1 #define mid int m=(r+l)>>1 #define LL long long #define ull unsigned long long #define mem0(x) memset(x,0,sizeof(x)) #define mem1(x) memset(x,-1,sizeof(x)) #define meminf(x) memset(x,INF,sizeof(x)) #define lowbit(x) (x&-x) const LL mod = 1000000; const int prime = 999983; const int INF = 0x3f3f3f3f; const int INFF = 1e9; const double pi = 3.141592653589793; const double inf = 1e18; const double eps = 1e-10; struct Edge{ int v,next; }edge[1000005]; int head[MAX]; int col[MAX]; int match[MAX]; int used[MAX]; int tot; void add_edge(int a,int b){ edge[tot]=(Edge){b,head[a]}; head[a]=tot++; } void init(){ mem1(head); mem1(match); mem0(col); tot=0; } bool colour(int u){ for(int i=head[u];i!=-1;i=edge[i].next){ int v=edge[i].v; if(!col[v]){ col[v]=-col[u]; if(!colour(v)) return false; } else if(col[v]==col[u]) return false; } return true; } bool dfs(int u){ used[u]=1; for(int i=head[u];i!=-1;i=edge[i].next){ int v=edge[i].v; int w=match[v]; if(w<0||(!used[w]&&dfs(w))){ match[v]=u; match[u]=v; return true; } } return false; } int main(){ int n,m; while(~scanf("%d%d",&n,&m)){ init(); for(int i=0;i<m;i++){ int a,b; scanf("%d%d",&a,&b); add_edge(a,b); add_edge(b,a); } int flag=0; for(int i=1;i<=n;i++){ if(!col[i]){ col[i]=1; if(!colour(i)){ printf("No\n"); flag=1; break; } } } if(!flag){ int ans=0; for(int i=1;i<=n;i++){ if(match[i]<0){ mem0(used); if(dfs(i)) ans++; } } printf("%d\n",ans); } } return 0; }
hdu 1083
全裸的匈牙利一套带走
不说了
hdu 1281
题意:就是每行每列只能有一个车,然后有些点可以放车,不能放的点不影响(和第一题棋盘不同,我开头看错了)
重要点就是如果这个地方不放,那么就会影响最大数量
题解:有了第一题的经验,这题很明显就是行列缩成一个点,车所在的点就是行列相交的地方,就是行(点)和列(点)之间连接的边,然后墙壁不影响,更容易了,直接给你的坐标建图,然后先求出最大匹配,再枚举删除每条边,如果得到的匹配数不等于最大匹配,那么这个点就是重要点。
#include <map> #include <set> #include <stack> #include <queue> #include <cmath> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <cstring> #include <sstream> #include <cstdlib> #include <iostream> #include <algorithm> #pragma comment(linker,"/STACK:102400000,102400000") using namespace std; #define MAX 205 #define MAXN 2000005 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define lrt rt<<1 #define rrt rt<<1|1 #define mid int m=(r+l)>>1 #define LL long long #define ull unsigned long long #define mem0(x) memset(x,0,sizeof(x)) #define mem1(x) memset(x,-1,sizeof(x)) #define meminf(x) memset(x,INF,sizeof(x)) #define lowbit(x) (x&-x) const LL mod = 1000000; const int prime = 999983; const int INF = 0x3f3f3f3f; const int INFF = 1e9; const double pi = 3.141592653589793; const double inf = 1e18; const double eps = 1e-10; int edge[MAX][MAX]; int match[MAX]; int used[MAX]; int n,m,k; void init(){ mem0(edge); mem1(match); } bool dfs(int u){ used[u]=1; for(int i=1;i<=n+m;i++){ if(!edge[u][i]) continue; int w=match[i]; if(w<0||(!used[w]&&dfs(w))){ match[i]=u; match[u]=i; return true; } } return false; } int main(){ int kase=0; while(~scanf("%d%d%d",&n,&m,&k)){ init(); kase++; for(int i=0;i<k;i++){ int a,b; scanf("%d%d",&a,&b); edge[a][b+n]=edge[b+n][a]=1; } int ans=0; for(int i=1;i<=n+m;i++){ if(match[i]<0){ mem0(used); if(dfs(i)) ans++; } } int ret=0; for(int i=1;i<=n+m;i++){ for(int j=i+1;j<=n+m;j++){ if(edge[i][j]){ edge[i][j]=edge[j][i]=0; mem1(match); int tmp=0; for(int k=1;k<=n+m;k++){ if(match[k]<0){ mem0(used); if(dfs(k)) tmp++; } } if(tmp!=ans) ret++; edge[i][j]=edge[j][i]=1; } } } printf("Board %d have %d important blanks for %d chessmen.\n",kase,ret,ans); } return 0; }
hdu 2819
题意:给你一个01矩阵,问你能不能通过行列交换,使对角线都是1
题解:首先要知道,如果i行j列是1,那么就是点i->j+n有边,所以对角线都为1,就是说最多可以连n条边,就是最大匹配是n
把行和列缩点,求出最大匹配,如果是n,然后就是看交换多少次,要知道变成对角线为1,只用行变换或者只用列变换都可以做到,所以枚举每行,看对应的行match的是不是这一列,如果不是就找下面满足的行和它换(其实挺水的,一开始没认真做,题目都没怎么搞懂)
#include <map> #include <set> #include <stack> #include <queue> #include <cmath> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <cstring> #include <sstream> #include <cstdlib> #include <iostream> #include <algorithm> #pragma comment(linker,"/STACK:102400000,102400000") using namespace std; #define MAX 205 #define MAXN 2000005 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define lrt rt<<1 #define rrt rt<<1|1 #define mid int m=(r+l)>>1 #define LL long long #define ull unsigned long long #define mem0(x) memset(x,0,sizeof(x)) #define mem1(x) memset(x,-1,sizeof(x)) #define meminf(x) memset(x,INF,sizeof(x)) #define lowbit(x) (x&-x) const LL mod = 1000000; const int prime = 999983; const int INF = 0x3f3f3f3f; const int INFF = 1e9; const double pi = 3.141592653589793; const double inf = 1e18; const double eps = 1e-10; struct Edge{ int v,next; }edge[20005]; int head[MAX]; int row[MAX]; int match[MAX]; int used[MAX]; int tot; void add_edge(int a,int b){ edge[tot]=(Edge){b,head[a]}; head[a]=tot++; } void init(){ mem1(head); mem1(match); mem0(row); tot=0; } bool dfs(int u){ used[u]=1; for(int i=head[u];i!=-1;i=edge[i].next){ int v=edge[i].v; int w=match[v]; if(w<0||(!used[w]&&dfs(w))){ match[v]=u; match[u]=v; return true; } } return false; } int main(){ int n; while(~scanf("%d",&n)){ init(); for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ int a; scanf("%d",&a); if(a){ add_edge(i,j+n); add_edge(j+n,i); } } } int ans=0; for(int i=1;i<=2*n;i++){ if(match[i]<0){ mem0(used); if(dfs(i)) ans++; } } if(ans!=n) printf("-1\n"); else{ int ret=0; for(int i=1;i<=n;i++){ int k=match[i]-n; if(k==i) continue; for(int j=i+1;j<=n;j++){ int t=match[j]-n; if(t==i){ ret++; row[i]=j; swap(match[i],match[j]); } } } printf("%d\n",ret); for(int i=1;i<=n;i++){ if(row[i]) printf("R %d %d\n",i,row[i]); } } } return 0; }
hdu 2389
题意:就是给你6000个点,分成两堆,给你坐标,然后给你人的速度,如果在时间内这个人能跑到伞所在的坐标,那么这两个点之间就有边,这样的话最多有3000×3000的边
匈牙利就会TLE,用网络流的ISAP要建反向边1800W伤不起,所以就去学了HK算法,详见http://www.cnblogs.com/penseur/archive/2013/06/16/3138981.html
#include <map> #include <set> #include <stack> #include <queue> #include <cmath> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <cstring> #include <sstream> #include <cstdlib> #include <iostream> #include <algorithm> #pragma comment(linker,"/STACK:102400000,102400000") using namespace std; #define MAX 6005 #define MAXN 2000005 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define lrt rt<<1 #define rrt rt<<1|1 #define mid int m=(r+l)>>1 #define LL long long #define ull unsigned long long #define mem0(x) memset(x,0,sizeof(x)) #define mem1(x) memset(x,-1,sizeof(x)) #define meminf(x) memset(x,INF,sizeof(x)) #define lowbit(x) (x&-x) const LL mod = 1000000; const int prime = 999983; const int INF = 0x3f3f3f3f; const int INFF = 1e9; const double pi = 3.141592653589793; const double inf = 1e18; const double eps = 1e-10; /**************读入外挂**********************/ inline int read_int(){ int ret=0; char tmp; while(!isdigit(tmp=getchar())); do{ ret=(ret<<3)+(ret<<1)+tmp-'0'; }while(isdigit(tmp=getchar())); return ret; } /*******************************************/ struct Edge{ int v,next; }edge[10000005]; struct Node{ int x,y,z; }node[MAX]; int head[MAX]; int match[MAX]; int used[MAX]; int dis[MAX]; int tot; int T,t,n,m,diss; void add_edge(int a,int b){ edge[tot]=(Edge){b,head[a]}; head[a]=tot++; } void init(){ mem1(head); mem1(match); tot=0; } bool bfs(){ diss=INF; mem1(dis); queue<int> q; for(int i=1;i<=n;i++){ if(match[i]==-1){ dis[i]=0; q.push(i); } } while(!q.empty()){ int u=q.front(); q.pop(); if(dis[u]>diss) break; for(int i=head[u];i!=-1;i=edge[i].next){ int v=edge[i].v; if(dis[v]==-1){ dis[v]=dis[u]+1; if(match[v]==-1) diss=dis[v]; else { dis[match[v]]=dis[v]+1; q.push(match[v]); } } } } return diss!=INF; } bool dfs(int u){ for(int i=head[u];i!=-1;i=edge[i].next){ int v=edge[i].v; if(!used[v]&&dis[v]==dis[u]+1){ used[v]=1; if(match[v]!=-1&&diss==dis[v]) continue; if(match[v]==-1||dfs(match[v])){ match[u]=v; match[v]=u; return true; } } } return false; } int MaxMatch(){ int ans=0; while(bfs()){ mem0(used); for(int i=1;i<=n;i++){ if(match[i]==-1&&dfs(i)) ans++; } } return ans; } int main(){ scanf("%d",&T); for(int kase=1;kase<=T;kase++){ scanf("%d%d",&t,&n); init(); for(int i=1;i<=n;i++){ int a,b,c; scanf("%d%d%d",&a,&b,&c); node[i]=(Node){a,b,c}; } scanf("%d",&m); for(int i=1;i<=m;i++){ int a,b; scanf("%d%d",&a,&b); for(int j=1;j<=n;j++){ int aa=node[j].x-a; int bb=node[j].y-b; int cc=t*node[j].z; if(cc*cc>=aa*aa+bb*bb){ add_edge(j,i+n); } } } printf("Scenario #%d:\n%d\n\n",kase,MaxMatch()); } return 0; }
题解:套一发模板就好了