二分图,网络流与费用流
二分图
-
加超级源点与超级汇点,dinic做;
-
匈牙利算法
vector<int> gra[maxn]; void addedge(int u,int v){ gra[u].push_back(v); gra[v].push_back(u); } int match[maxn]; bool used[maxn]; bool dfs(int u){ used[u]=true; for(int i=0;i<(int)gra[u].size();i++){ int v=gra[u][i]; int w=match[v]; if(!w||!used[w]&&dfs(w)){ match[u]=v; match[v]=u; return true; } } return false; } int solve(){ int res=0; memset(match,0,sizeof(match)); for(int i=1;i<=n;i++){ memset(used,0,sizeof(used)); if(dfs(i)) res++; } return res; }
poj3057
考虑(门d,时间T)这个二元组,对每个二元组,有确定的人可以恰巧在时间T通过门d(只要人离门的距离小于等于T即可),并且对这个确定的二元组只能通过一个人,对(二元组 | 可以通过的人)建二分图,按照T从小到大对二元组做匈牙利算法(dfs(i)),一旦匹配的边数等于人数,此时的T即为最小时间。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
char s[15][15];
const int maxn=6000;
vector<int> gra[maxn];
void addedge(int u,int v){
gra[u].push_back(v);
gra[v].push_back(u);
}
int match[maxn];
bool used[maxn];
bool dfs(int u){
used[u]=true;
for(int i=0;i<(int)gra[u].size();i++){
int v=gra[u][i];
int w=match[v];
if(!w||!used[w]&&dfs(w)){
match[u]=v;
match[v]=u;
return true;
}
}
return false;
}
int x,y;
int dis[150][150];
struct pos{
int x,y;
int d;
};
vector<pos> posd,posp;
int id[15][15];
int dx[]={1,0,-1,0},dy[]={0,1,0,-1};
bool inside(int a,int b){
return a>=1&&a<=x&&b>=1&&b<=y;
}
int bfs(int S){
bool vis[15][15]={0};
queue<pos> que;
posd[S].d=0;
que.push(posd[S]);
while(!que.empty()){
pos u=que.front(); que.pop();
for(int i=0;i<4;i++){
int vx=u.x+dx[i],vy=u.y+dy[i];
if(inside(vx,vy)&&s[vx][vy]=='.'&&dis[S][id[vx][vy]]==0x3f3f3f3f){
dis[S][id[vx][vy]]=u.d+1;
pos tmp;
tmp.d=u.d+1; tmp.x=vx; tmp.y=vy;
que.push(tmp);
}
}
}
}
void solve(){
posd.clear(); posp.clear();
for(int i=0;i<maxn;i++) gra[i].clear();
cin >> x >> y;
for(int i=1;i<=x;i++) cin >> s[i]+1;
for(int i=1;i<=x;i++){
for(int j=1;j<=y;j++){
if(s[i][j]=='.'){
pos tmp; tmp.x=i,tmp.y=j;
posp.push_back(tmp);
id[i][j]=posp.size()-1;
}
if(s[i][j]=='D'){
pos tmp; tmp.x=i,tmp.y=j;
posd.push_back(tmp);
id[i][j]=posd.size()-1;
}
}
}
int np=posp.size(),nd=posd.size();
if(!np){
cout << 0 << endl; return;
}
memset(dis,0x3f3f3f3f,sizeof(dis));
for(int i=0;i<nd;i++){
bfs(i);
}
for(int T=1;T<=np;T++)
for(int i=0;i<nd;i++){
for(int j=0;j<np;j++){
if(dis[i][j]<=T) addedge((T-1)*nd+i+1,np*nd+1+j);
}
}
memset(match,0,sizeof(match));
int num=0;
for(int i=1;i<=np*nd;i++){
memset(used,0,sizeof(used));
if(dfs(i)){
num++;
if(num==np){
cout << (i+nd-1)/nd << endl; return;
}
}
}
cout << "impossible" << endl;
}
int main(){
int t;
cin >> t;
while(t--) solve();
}
poj3281
建图方式很经典。
poj3041
经典二分图。
二分图:最小点覆盖=最大匹配。
poj1486
枚举边,如若减去这条边以后最大匹配变小了,说明这条边是必须的。
洛谷p1963变换序列
关于最小字典序的处理。保证左边每个点的存边都是升序的。从后往前匹配。
最大字典序的话应该是左边每个点存边都是降序的,同样从后往前匹配。
洛谷p3231消毒
尚未理解。
最大流
FF算法+dinic优化+弧优化
理论复杂度O(VE),实际上更快。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
const int maxn=300;
struct edge{
int flow,to;
int rev;
};
vector<edge> gra[maxn];
void addedge(int u,int v,int f){
edge tmp;
tmp.flow=f; tmp.to=v;
gra[u].push_back(tmp);
tmp.flow=0; tmp.to=u;
gra[v].push_back(tmp);
int sizu=gra[u].size(),sizv=gra[v].size();
gra[u][sizu-1].rev=sizv-1;
gra[v][sizv-1].rev=sizu-1;
}
int dep[maxn],s,t;
bool bfs(){
memset(dep,0,sizeof(dep));
dep[s]=1;
queue<int> que;
que.push(s);
while(!que.empty()){
int u=que.front();
que.pop();
int siz=gra[u].size();
for(int i=0;i<siz;i++){
edge v=gra[u][i];
int vv=v.to;
if(dep[vv]||!v.flow) continue;
dep[vv]=dep[u]+1;
que.push(vv);
}
}
return dep[t];
}
int now[maxn];
int dfs(int cur,int flow){
if(cur==t||!flow) return flow;
int f;
for(int i=now[cur];i<(int)gra[cur].size();i++){
edge &v=gra[cur][i];
int vv=v.to;
if(dep[vv]==dep[cur]+1&&(f=dfs(vv,min(flow,v.flow)))){
v.flow-=f;
gra[vv][v.rev].flow+=f;
return f;
}
now[cur]=i;
}
return 0;
}
int dinic(){
int flow=0,f;
while(bfs()){
memset(now,0,sizeof(now));
while(f=dfs(s,0x3f3f3f3f)) flow+=f;
}
return flow;
}
poj2112
弗洛伊德预处理最短路+二分+最大流判断。 水。