网络流

最大流与最小割

POJ 3713: Transferring Sylla
网络流暴力做法:枚举起点终点作为源点和汇点,拆点建图,判断是否可以连续增广三次。
当然这样复杂度约为O(3n^2m),肯定会TLE。
有人靠只枚举终点(固定0号点为起点)的方式AC了,但对于如下数据,似乎会出错。

3 6
0 1
0 1
0 1
0 2
0 2
0 2

正解如下:
引理:至少有三条独立路径(除去起点和终点相同)等价于图的点连通度(点连通图为至少删掉多少个点,使得图不连通)大于等于三。
形而上的理解:每删掉一个点,独立路径数就减一,因此两条件等价。
因此,只要枚举删掉一个点,然后判断剩下的图是否是点双联通的即可。(即是否存在割点,不存在,则可行)
具体实现上,求割点用tarjan即可。时间复杂度O(nm)
代码如下

/*

*/
#define method_1
#ifdef method_1
/*
网络流暴力做法:枚举起点终点作为源点和汇点,拆点建图,判断是否可以连续增广三次。 
当然这样复杂度约为O(3n^2*m),肯定会TLE。
有人靠只枚举终点(固定0号点为起点)的方式AC了,但对于如下数据,似乎会出错。
3 6
0 1
0 1
0 1
0 2
0 2
0 2
正解如下:
引理:至少有三条独立路径(除去起点和终点相同)等价于图的点连通度(点连通图为至少删掉多少个点,使得图不连通)大于等于三。 
形而上的理解:每删掉一个点,独立路径数就减一,因此两条件等价。
因此,只要枚举删掉一个点,然后判断剩下的图是否是点双联通的即可。(即是否存在割点,不存在,则可行)
具体实现上,求割点用tarjan即可。时间复杂度O(n*m)。 
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#include<ctime>
#include<string>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=500+5;
const int maxm=20000+5;
const int INF=0x3f3f3f3f;
int n,m,tot,num,root,dfn[maxn],low[maxn],head[maxn],f[maxm],t[maxm],cut[maxn];
struct node{
    int from,to;
}edge[maxm<<1];
void add(int from,int to){
    edge[++tot].from=head[from],head[from]=tot,edge[tot].to=to; 
}
void init(int x){
    tot=1,num=0;
    memset(head,0,sizeof(head));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(cut,0,sizeof(cut));
    if(x==1) root=2;
    else root=1;
}
void tarjan(int x){
    dfn[x]=low[x]=++num;
    int flag=0;
    for(int i=head[x];i;i=edge[i].from){
        int y=edge[i].to;
        if(!dfn[y]){
            tarjan(y);
            low[x]=min(low[x],low[y]);
            if(low[y]>=dfn[x]){
                flag++;
                if(x!=root||flag>1) cut[x]=1;
            }
        }
        else low[x]=min(low[x],dfn[y]);
    }
}
int main() {
//  ios::sync_with_stdio(false);
    //freopen("Transferring Sylla.in","r",stdin);
    while(cin>>n>>m){
        if(!n&&!m) break;
        for(int i=1;i<=m;i++){
            scanf("%d%d",&f[i],&t[i]);
            f[i]++,t[i]++;
        }
        if(n==1){
            if(m>=3) cout<<"YES"<<endl;
            else cout<<"NO"<<endl;
            continue;
        }
        bool flag;
        for(int i=1;i<=n;i++){
            init(i);
            flag=true;
            for(int j=1;j<=m;j++){
                if(f[j]==i||t[j]==i) continue;
                add(f[j],t[j]),add(t[j],f[j]);
            }
            tarjan(root);
            for(int j=1;j<=n;j++){
                if(i==j) continue;
                if(!dfn[j]||cut[j]) flag=false;
                if(!flag) break;
            }
            if(!flag) break;
        }   
        if(!flag) cout<<"NO"<<endl;
        else cout<<"YES"<<endl;
    }
    return 0;
}
#endif
#ifdef method_2
/*

*/

#endif
#ifdef method_3
/*

*/

#endif

POJ 2987: Firing
最大权闭合子图模板,这里不再赘述。
值得说到的是,最大权闭合子图模型跑完dinic后,从s出发能够访问到的点,都是被选择的点,同时点数满足最少。
因此只要从源点dfs一次,沿着所有没有满流的边能跑到的点集,就是答案集合。
最后,因为少了一个地方没有改成long long,WA了好久。
代码如下

/*

*/
#define method_1
#ifdef method_1
/*
最大权闭合子图模板,这里不再赘述。
值得说到的是,最大权闭合子图模型跑完dinic后,从s出发能够访问到的点,都是被选择的点,同时点数满足最少。
因此只要从源点dfs一次,沿着所有没有满流的边能跑到的点集,就是答案集合。
最后,因为少了一个地方没有改成long long,WA了好久。 
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#include<ctime>
#include<string>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=5000+5;
const int maxm=(5000*2+60000)*2+5;
const ll INF=0x3f3f3f3f3f3f3f3fll;
struct node{
    int from,to;
    ll cap;
}edge[maxm];
int n,m,head[maxn],tot=1,d[maxn],s,t,vis[maxn];
ll ans=0,maxflow=0;
void add(int from,int to,ll cap){
    edge[++tot].from=head[from],head[from]=tot,edge[tot].to=to,edge[tot].cap=cap;
    edge[++tot].from=head[to],head[to]=tot,edge[tot].to=from,edge[tot].cap=0;
}
queue<int>q;
bool bfs(){
    memset(d,0,sizeof(d));
    while(q.size()) q.pop();
    q.push(s);d[s]=1;
    while(q.size()){
        int x=q.front();q.pop();
        for(int i=head[x];i;i=edge[i].from){
            int y=edge[i].to;
            ll cap=edge[i].cap;
            if(cap&&!d[y]){
                d[y]=d[x]+1;
                q.push(y);
                if(y==t) return 1;
            }
        }
    }
    return 0;
}
ll dinic(int x,ll flow){ //注意把int改为longlong的时候,一定要注意函数返回值。 
    if(x==t) return flow;
    ll rest=flow,k;
    for(int i=head[x];i&&rest;i=edge[i].from){
        int y=edge[i].to;
        if(edge[i].cap&&d[y]==d[x]+1){
            k=dinic(y,min(rest,edge[i].cap));
            if(!k) d[y]=0;
            edge[i].cap-=k;
            edge[i^1].cap+=k;
            rest-=k;
        }
    }
    return flow-rest;
}
void dfs(int x){
    vis[x]=1;
    for(int i=head[x];i;i=edge[i].from){
        int y=edge[i].to;
        if(edge[i].cap&&!vis[y]){
            dfs(y);
        }
    }
}
int main() {
    ios::sync_with_stdio(false);
    //freopen("Firing.in","r",stdin);
    cin>>n>>m;
    s=0,t=n+1;
    int from,to;
    ll cap;
    for(int i=1;i<=n;i++){
        cin>>cap;
        if(cap>=0){
            add(s,i,cap);
            ans+=cap;
        }
        else add(i,t,-cap);
    }
    for(int i=1;i<=m;i++) cin>>from>>to,add(from,to,INF);
    ll flow=0;
    while(bfs()){
        while(flow=dinic(s,INF)) maxflow+=flow;
    }
    int cnt=0;
    dfs(s);
    for(int i=1;i<=n;i++) if(vis[i]) cnt++;
    cout<<cnt<<" "<<ans-maxflow; 
    return 0;
}
#endif
#ifdef method_2
/*

*/

#endif
#ifdef method_3
/*

*/

#endif

POJ 2914: Minimum Cut
求全局最小割,Stoer-Wagner算法。
题解链接:https://blog.csdn.net/yihuikang/article/details/7730986?locationNum=5
代码如下

/*
求全局最小割,Stoer-Wagner算法。 
题解链接:https://blog.csdn.net/yihuikang/article/details/7730986?locationNum=5
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#include<ctime>
#include<string>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=500+5;
const int INF=0x3f3f3f3f;
int n,m,G[maxn][maxn],f[maxn],v[maxn],dis[maxn],min_cut;
void init(){
    memset(G,0,sizeof(G));
    min_cut=INF;
}
void mincut(){
    for(int i=1;i<=n;i++) f[i]=i;
    int maxj,pre;
    while(n>1){
        memset(v,0,sizeof(v));v[f[1]]=1;maxj=2;pre=1; //将1号点所在集合放入最大生成树 
        for(int i=2;i<=n;i++){
            dis[f[i]]=G[f[1]][f[i]];
            if(dis[f[i]]>dis[f[maxj]]) maxj=i;
        }
        for(int i=2;i<=n;i++){
            if(i==n){ //生成树处理到最后一条边 
                min_cut=min(min_cut,dis[f[maxj]]);
//              D(maxj);D(dis[f[maxj]]);E;
                for(int j=1;j<=n;j++){
                    G[f[j]][f[pre]]+=G[f[j]][f[maxj]]; //合并进入集合 
                    G[f[pre]][f[j]]=G[f[j]][f[pre]];
                }
                f[maxj]=f[n--]; //删除maxj 相当于合并 
            }
            v[f[maxj]]=1;pre=maxj;maxj=-1; //将maxj所在集合并入最大生成树 
            for(int j=2;j<=n;j++){
                if(!v[f[j]]){
                    dis[f[j]]+=G[f[pre]][f[j]];
                    if(maxj==-1||dis[f[j]]>dis[f[maxj]]) maxj=j;
                }
            }
        }
    }
}
int main() {
//  ios::sync_with_stdio(false);
    //freopen("Minimum Cut.in","r",stdin);
    while(cin>>n>>m){
        init();
        int from,to,v;
        for(int i=1;i<=m;i++){
            scanf("%d%d%d",&from,&to,&v);
            from++,to++;
            G[from][to]+=v;G[to][from]+=v;
        }
        mincut();
        min_cut==INF?cout<<0:cout<<min_cut;
        cout<<endl;
    }
    return 0;
}

POJ 3155: Hard Life
01分数规划+最大密度子图,精度丧心病狂,二分的精度放到1e-8就会WA。
另外double的最大值取0x3f3f3f3f,不要取1e18,否则会WA,原因未知。
建图时,对于mid,化边为点(编号n+1~n+m+1),源点向n+i建边,容量1。
n+i向对应边的两个端点建边,容量INF。
i向汇点建边,容量mid。
最后判断m-最小割是否大于eps即可。
代码如下

/*
01分数规划+最大密度子图,精度丧心病狂,二分的精度放到1e-8就会WA。 
另外double的最大值取0x3f3f3f3f,不要取1e18,否则会WA,原因未知。 
建图时,对于mid,化边为点(编号n+1~n+m+1),源点向n+i建边,容量1。
n+i向对应边的两个端点建边,容量INF。
i向汇点建边,容量mid。
最后判断m-最小割是否大于eps即可。 
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#include<ctime>
#include<string>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=100+1000+5;
const int maxm=(1000*3+100)*2+5;
const double eps=1e-6;
const double eps_lim=1e-5;
const int INF=0x3f3f3f3f;
//const double INF=1e18;
struct node{
    int from,to;
    double cap;
}edge[maxm];
int n,m,head[maxn],tot=1,d[maxn],s,t,vis[maxn],from[maxn],to[maxn],ans=0;
double maxflow;
void init(){
    maxflow=m;
    memset(head,0,sizeof(head));
    tot=1;
}
void add(int from,int to,double cap){
    edge[++tot].from=head[from],head[from]=tot,edge[tot].to=to,edge[tot].cap=cap;
    edge[++tot].from=head[to],head[to]=tot,edge[tot].to=from,edge[tot].cap=0;
}
queue<int>q;
bool bfs(){
    memset(d,0,sizeof(d));
    while(q.size()) q.pop();
    q.push(s);d[s]=1;
    while(q.size()){
        int x=q.front();q.pop();
        for(int i=head[x];i;i=edge[i].from){
            int y=edge[i].to;
            double cap=edge[i].cap;
            if(cap>eps&&!d[y]){
                d[y]=d[x]+1;
                q.push(y);
                if(y==t) return 1;
            }
        }
    }
    return 0;
}
double dinic(int x,double flow){ //注意把int改为longlong的时候,一定要注意函数返回值。 
    if(x==t) return flow;
    double rest=flow,k;
    for(int i=head[x];i&&rest>eps;i=edge[i].from){
        int y=edge[i].to;
        if(edge[i].cap>eps&&d[y]==d[x]+1){
            k=dinic(y,min(rest,edge[i].cap));
            if(k<eps) d[y]=0;
            edge[i].cap-=k;
            edge[i^1].cap+=k;
            rest-=k;
        }
    }
    return flow-rest;
}
bool check(double mid){
    init();
    for(int i=1;i<=m;i++){
        add(s,n+i,1);add(n+i,from[i],INF);add(n+i,to[i],INF);
    }
    for(int i=1;i<=n;i++) add(i,t,mid);
    while(bfs()){
        maxflow-=dinic(s,INF);
    }
    return maxflow>eps;
}
void dfs(int x){
    vis[x]=1;
    if(x>=1&&x<=n) ans++;
    for(int i=head[x];i;i=edge[i].from){
        int y=edge[i].to;
        if(edge[i].cap>0&&!vis[y]){
            dfs(y);
        }
    }
}
int main() {
    ios::sync_with_stdio(false);
    //freopen("Hard Life.in","r",stdin);
    cin>>n>>m;
    if(m==0){
        cout<<1<<endl<<1<<endl;
        return 0;
    }
    s=0,t=n+m+1;
    for(int i=1;i<=m;i++) cin>>from[i]>>to[i];
    double l=0,r=m,ans_lim;
    while(r-l>eps_lim){
        double mid=(l+r)/2;
        if(check(mid)) l=mid,ans_lim=mid;
        else r=mid;
    }
    check(l);dfs(s);
    cout<<ans<<endl;
    for(int i=1;i<=n;i++) if(vis[i]) cout<<i<<endl;
    return 0;
}

二分图匹配

POJ 1274: The Perfect Stall
二分图匹配模板题,不再赘述。
代码如下

/*

*/
#define method_1
#ifdef method_1
/*
二分图匹配模板题,不再赘述。 
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#include<ctime>
#include<string>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=200+5;
const int maxm=200+5;
const int INF=0x3f3f3f3f;
int n,m,match[maxm],vis[maxn],G[maxn][maxm],ans;
bool dfs(int x){
    for(int i=1;i<=m;i++){
        if(G[x][i]&&!vis[i]){
            vis[i]=1;
            if(!match[i]||dfs(match[i])){
                match[i]=x;
                return true;
            }
        }
    }
    return false;
}
void init(){
    memset(match,0,sizeof(match));
    memset(G,0,sizeof(G));
    ans=0;
}
int main() {
    ios::sync_with_stdio(false);
//  freopen("The Perfect Stall.in","r",stdin);
    while(cin>>n>>m){
        int num,temp;
        init();
        for(int i=1;i<=n;i++){
            cin>>num;
            while(num--){
                cin>>temp;
                G[i][temp]=1;
            }
        }
        for(int i=1;i<=n;i++){
            memset(vis,0,sizeof(vis));
            if(dfs(i)) ans++;
        }
        cout<<ans<<endl;
    }
    return 0;
}
#endif
#ifdef method_2
/*

*/

#endif
#ifdef method_3
/*

*/

#endif

POJ 2112: Optimal Milking
题目要求最大值尽量小,因此二分答案。
首先floyd跑出任意两点最短距离。
对于每个答案mid,在所有距离小于等于mid的、不属于同一集合的点之间建边。另外,源点向奶牛建边,容量为1。机器向汇点建边,容量为M。
接下来用dinic求一下这个二分图的多重匹配是否等于C即可。
代码如下

/*

*/
#define method_1
#ifdef method_1
/*
题目要求最大值尽量小,因此二分答案。
首先floyd跑出任意两点最短距离。 
对于每个答案mid,在所有距离小于等于mid的、不属于同一集合的点之间建边。另外,源点向奶牛建边,容量为1。机器向汇点建边,容量为M。 
接下来用dinic求一下这个二分图的多重匹配是否等于C即可。 
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#include<ctime>
#include<string>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=230+5;
const int maxm=(30*200+30+200)*2+5;
const int INF=0x3f3f3f3f;
struct node{
    int from,to,cap;
}edge[maxm];
int K,C,M,head[maxn],tot,d[maxn],s,t,maxflow,dis[maxn][maxn];
void add(int from,int to,int cap){
    edge[++tot].from=head[from],head[from]=tot,edge[tot].to=to,edge[tot].cap=cap;
    edge[++tot].from=head[to],head[to]=tot,edge[tot].to=from,edge[tot].cap=0;
}
queue<int>q;
bool bfs(){
    memset(d,0,sizeof(d));
    while(q.size()) q.pop();
    q.push(s);d[s]=1;
    while(q.size()){
        int x=q.front();q.pop();
        for(int i=head[x];i;i=edge[i].from){
            int y=edge[i].to,cap=edge[i].cap;
            if(cap&&!d[y]){
                d[y]=d[x]+1;
                q.push(y);
                if(y==t) return 1;
            }
        }
    }
    return 0;
}
int dinic(int x,int flow){ 
    if(x==t) return flow;
    int rest=flow,k;
    for(int i=head[x];i&&rest;i=edge[i].from){
        int y=edge[i].to;
        if(edge[i].cap&&d[y]==d[x]+1){
            k=dinic(y,min(rest,edge[i].cap));
            if(!k) d[y]=0;
            edge[i].cap-=k;
            edge[i^1].cap+=k;
            rest-=k;
        }
    }
    return flow-rest;
}
void init(){
    memset(head,0,sizeof(head));
    maxflow=0,tot=1;
}
bool check(int mid){
    init();
    for(int i=K+1;i<=K+C;i++) add(s,i,1);
    for(int i=K+1;i<=K+C;i++) for(int j=1;j<=K;j++) if(dis[i][j]<=mid) add(i,j,INF);
    for(int j=1;j<=K;j++) add(j,t,M);
    int flow=0;
    while(bfs()){
        while(flow=dinic(s,INF)) maxflow+=flow;
    }
    return maxflow==C;
}
int main() {
    ios::sync_with_stdio(false);
    //freopen("Optimal Milking.in","r",stdin);
    cin>>K>>C>>M;
    s=0,t=K+C+1;
    memset(dis,INF,sizeof(dis));
    //源点 0
    //汇点 K+C+1
    //牛(左侧节点) K+1~K+C
    //机器(右侧节点 1~K  
    for(int i=1;i<=K+C;i++) for(int j=1;j<=K+C;j++){
        cin>>dis[i][j];
        if(i!=j&&dis[i][j]==0) dis[i][j]=INF;
    }
    for(int k=1;k<=K+C;k++) for(int i=1;i<=K+C;i++) for(int j=1;j<=K+C;j++) dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
    /*
    for(int i=1;i<=K+C;i++){
        for(int j=1;j<=K+C;j++){
            cout<<dis[i][j]<<" ";
        }
        cout<<endl;
    }
    */
    int l=0,r=INF;
    while(l<r){
        int mid=l+r>>1;
        if(check(mid)) r=mid;
        else l=mid+1;
    }
    cout<<l;
    return 0;
}
#endif
#ifdef method_2
/*

*/

#endif
#ifdef method_3
/*

*/

#endif

POJ 1486: Sorting Slides
题解链接://www.greatytc.com/p/4d3f099b0254
POJ 1466: Girls and Boys
由于分男女,所以是二分图,黑白染色后求一下最大独立集即可。
最大独立集=点数-最大匹配。
代码如下

/*

*/
#define method_1
#ifdef method_1
/*
由于分男女,所以是二分图,黑白染色后求一下最大独立集即可。
最大独立集=点数-最大匹配。 
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#include<ctime>
#include<string>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=500+5;
const int INF=0x3f3f3f3f;
int n,match[maxn],vis[maxn],G[maxn][maxn],ans,c[maxn];
bool dfs(int x){
    for(int i=1;i<=n;i++){
        if(G[x][i]&&!vis[i]&&c[i]==2){
            vis[i]=1;
            if(!match[i]||dfs(match[i])){
                match[i]=x;
                return true;
            }
        }
    }
    return false;
}
void init(){
    memset(match,0,sizeof(match));
    memset(G,0,sizeof(G));
    memset(c,0,sizeof(c));
    ans=0;
}
void color(int x,int col){
    c[x]=col;
    for(int i=1;i<=n;i++){
        if(!c[i]&&G[x][i]) color(i,3-col);
    }
}
int main() {
//  ios::sync_with_stdio(false);
    //freopen("Girls and Boys.in","r",stdin);
    while(cin>>n){
        init();
        int ignore_num,num,temp;
        for(int i=1;i<=n;i++){
            scanf("%d: (%d)",&ignore_num,&num);
            while(num--){
                cin>>temp;
                temp++;
                G[i][temp]=G[temp][i]=1;
            }
        }
        for(int i=1;i<=n;i++){
            if(!c[i]) color(i,1);
        }
        for(int i=1;i<=n;i++){
            memset(vis,0,sizeof(vis));
            if(c[i]==1&&dfs(i)) ans++;
        }
        cout<<n-ans<<endl;
    }
    return 0;
}
#endif
#ifdef method_2
/*

*/

#endif
#ifdef method_3
/*

*/

#endif

POJ 3692: Kindergarten
二分图最大团问题,转化为求补图的最大匹配。答案就是G+B-补图的最大匹配。
原理:
转化为补图,补图中有边的说明是不认识的,那么想要剩下的人都认识,就得把边去掉,也就是去掉最少的点使所有的边都去掉,就是最小路径覆盖。
最小路径覆盖数等于最大二分配匹数。
代码如下

/*

*/
#define method_1
#ifdef method_1
/*
二分图最大团问题,转化为求补图的最大匹配。答案就是G+B-补图的最大匹配。
原理:
转化为补图,补图中有边的说明是不认识的,那么想要剩下的人都认识,就得把边去掉,也就是去掉最少的点使所有的边都去掉,就是最小路径覆盖。
最小路径覆盖数等于最大二分配匹数。 
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#include<ctime>
#include<string>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=200+5;
const int INF=0x3f3f3f3f;
int G,B,M,kase=0,match[maxn*2],vis[maxn*2],g[maxn*2][maxn*2],ans;
bool dfs(int x){
    for(int i=G+1;i<=G+B;i++){
        if(g[x][i]&&!vis[i]){
            vis[i]=1;
            if(!match[i]||dfs(match[i])){
                match[i]=x;
                return true;
            }
        }
    }
    return false;
}
void init(){
    memset(match,0,sizeof(match));
    memset(g,0,sizeof(g));
    ans=0;
}
int main() {
    ios::sync_with_stdio(false);
    //freopen("Kindergarten.in","r",stdin);
    while(cin>>G>>B>>M){
        if(!G&&!B&&!M) break;
        cout<<"Case "<<++kase<<": ";
        init();
        int from,to;
        while(M--){
            cin>>from>>to;
            to+=G;
            g[from][to]=g[to][from]=1;
        }
        for(int i=1;i<=G+B;i++) for(int j=1;j<=G+B;j++) g[i][j]=1-g[i][j];
        /*
        for(int i=1;i<=G+B;i++){
            for(int j=1;j<=G+B;j++){
                cout<<g[i][j]<<" ";
            }
            cout<<endl;
        }
        */
        for(int i=1;i<=G;i++){
            memset(vis,0,sizeof(vis));
            if(dfs(i)) ans++;
        }
        cout<<G+B-ans<<endl;
    }
    return 0;
}
#endif
#ifdef method_2
/*

*/

#endif
#ifdef method_3
/*

*/

#endif

POJ 2724: Purifying Machine
题意读懂后,剩下的就简单了。
在只有一位不同的字符串之间建边。
注意到每一条边连接的结点一个二进制位有奇数个1,另一个有偶数个1,故为二分图。
答案就是二分图的边覆盖数,即点数-最大匹配。
代码如下

/*

*/
#define method_1
#ifdef method_1
/*
题意读懂后,剩下的就简单了。
在只有一位不同的字符串之间建边。
注意到每一条边连接的结点一个二进制位有奇数个1,另一个有偶数个1,故为二分图。
答案就是二分图的边覆盖数,即点数-最大匹配。 
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#include<ctime>
#include<string>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=10+5;
const int maxm=(1<<10)+5;
const int INF=0x3f3f3f3f;
int n,m,cnt,match[maxm],vis[maxm],G[maxm][maxm],ans,c[maxm];
string a[maxm],s[maxm];
map<string,int>mp;
void init(){
    mp.clear();
    memset(match,0,sizeof(match));
    memset(G,0,sizeof(G));
    memset(c,0,sizeof(c));
    cnt=0;
    ans=0;
}
void color(int x,int col){
    c[x]=col;
    for(int i=1;i<=cnt;i++){
        if(!c[i]&&G[x][i]) color(i,3-col);
    }
}
bool dfs(int x){
    for(int i=1;i<=cnt;i++){
        if(G[x][i]&&!vis[i]&&c[i]==2){
            vis[i]=1;
            if(!match[i]||dfs(match[i])){
                match[i]=x;
                return true;
            }
        }
    }
    return false;
}
bool check(int i,int j){ //判断s[i]和s[j]是否有且只有一位不同 
    int num=0;
    for(int k=0;k<=n-1;k++) if(s[i][k]!=s[j][k]) num++;
    return num==1;
}
int main() {
    ios::sync_with_stdio(false);
    //freopen("Purifying Machine.in","r",stdin);
    while(cin>>n>>m){
        if(!n&&!m) break;
        init();
        for(int i=1;i<=m;i++){
            cin>>a[i];
        }
        for(int i=1;i<=m;i++){
            int pos=a[i].find("*");
            if(pos==string::npos){
                if(mp[a[i]]==0){
                    mp[a[i]]=++cnt;
                    s[cnt]=a[i];
                }
            }
            else{
                a[i][pos]='0';
                if(mp[a[i]]==0){
                    mp[a[i]]=++cnt;
                    s[cnt]=a[i];
                }
                a[i][pos]='1';
                if(mp[a[i]]==0){
                    mp[a[i]]=++cnt;
                    s[cnt]=a[i];
                }
            }
        }
        for(int i=1;i<=cnt;i++) for(int j=i+1;j<=cnt;j++) if(check(i,j)) G[i][j]=G[j][i]=1;
        /*
        for(int i=1;i<=cnt;i++){
            for(int j=1;j<=cnt;j++){
                cout<<G[i][j]<<" ";
            }
            cout<<endl;
        }
        */
        for(int i=1;i<=cnt;i++){
            if(!c[i]) color(i,1);
        }
        for(int i=1;i<=cnt;i++){
            memset(vis,0,sizeof(vis));
            if(c[i]==1&&dfs(i)) ans++;
        }
        cout<<cnt-ans<<endl;
    }
    return 0;
}
#endif
#ifdef method_2
/*

*/

#endif
#ifdef method_3
/*

*/

#endif

POJ 2226: Muddy Fields
比较巧妙的建图,蓝书上有,这里不赘述了。
代码如下

/*

*/
#define method_1
#ifdef method_1
/*

*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=1000+5;
const int INF=0x3f3f3f3f;
int n,m,G[maxn][maxn],mp[maxn][maxn],mp1[maxn][maxn],vis[maxn],match[maxn],cnt1=0,cnt2=0;
char a[maxn][maxn];
bool dfs(int x){
    for(int i=1;i<=cnt2;i++){
        if(G[x][i]&&!vis[i]){
            vis[i]=1;
            if(!match[i]||dfs(match[i])){
                match[i]=x;
                return true;
            } 
        }
    }
    return false;
}
void build(){
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(a[i][j]=='*'){
                if(a[i][j-1]=='*') mp[i][j]=mp[i][j-1];
                else mp[i][j]=++cnt1;
            }
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(a[i][j]=='*'){
                if(a[i-1][j]=='*') mp1[i][j]=mp1[i-1][j];
                else mp1[i][j]=++cnt2;
            }
            G[mp[i][j]][mp1[i][j]]=1;
        }
    }
}
int main() {
    ios::sync_with_stdio(false);
//  freopen("Muddy Fields.in","r",stdin);
    while(cin>>n>>m){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                cin>>a[i][j];
            }
        }
        cnt1=0,cnt2=0;
        memset(mp,0,sizeof(mp));
        memset(mp1,0,sizeof(mp1));
        memset(match,0,sizeof(match));
        memset(G,0,sizeof(G));
        build();
        /*
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                cout<<mp1[i][j];
            }
            cout<<endl;
        }
        */
        int ans=0;
        for(int i=1;i<=cnt1;i++){
            memset(vis,0,sizeof(vis));
            if(dfs(i)) ans++;
        }
        cout<<ans<<endl;
    }
    return 0;
}
#endif
#ifdef method_2
/*

*/

#endif
#ifdef method_3
/*

*/

#endif

最小费用流

POJ 3068: "Shortest" pair of paths
拆点建图,最后求流量为2的最小费用流即可。
代码如下

/*

*/
#define method_1
#ifdef method_1
/*
拆点建图,最后求流量为2的最小费用流即可。 
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#include<ctime>
#include<string>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=64*2+5;
const int maxm=10000*2+5;
const int INF=0x3f3f3f3f;
int n,m,head[maxn],d[maxn],v[maxn],incf[maxn],pre[maxn],maxflow,kase=0,ans,tot,s,t;
struct node{
    int from,to,cap,cost;
}edge[maxm+maxn];
void add(int from,int to,int cap,int cost){
    edge[++tot].from=head[from],head[from]=tot,edge[tot].to=to,edge[tot].cap=cap,edge[tot].cost=cost;
    edge[++tot].from=head[to],head[to]=tot,edge[tot].to=from,edge[tot].cap=0,edge[tot].cost=-cost;
}
void init(){
    memset(head,0,sizeof(head));
    memset(incf,0,sizeof(incf));
    memset(pre,0,sizeof(pre));
    tot=1;maxflow=2;ans=0;
}
bool spfa(){
    queue<int>q;
    memset(d,INF,sizeof(d));
    memset(v,0,sizeof(v));
    d[s]=0,v[s]=1,incf[s]=INF;
    q.push(s);
    while(q.size()){
        int x=q.front();q.pop();v[x]=0;
        for(int i=head[x];i;i=edge[i].from){
            int y=edge[i].to,cap=edge[i].cap,cost=edge[i].cost;
            if(cap){
                if(d[y]>d[x]+cost){
                    d[y]=d[x]+cost;
                    incf[y]=min(incf[x],cap);
                    pre[y]=i;
                    if(!v[y]){
                        v[y]=1;q.push(y);
                    }
                }
            }
        }
    }
    if(d[t]==INF) return false;
    return true;
}
void update(){
    int x=t;
    while(x!=s){
        int i=pre[x];
        edge[i].cap-=incf[t];
        edge[i^1].cap+=incf[t];
        x=edge[i^1].to;
    }
    maxflow-=incf[t];
    ans+=d[t]*incf[t];
}
int main() {
    ios::sync_with_stdio(false);
    //freopen("Shortest pair of paths.in","r",stdin);
    while(cin>>n>>m){
        if(!n&&!m) break;
        init();
        s=1,t=2*n;
        cout<<"Instance #"<<++kase<<": ";
        int from,to,cost;
        for(int i=1;i<=m;i++){
            cin>>from>>to>>cost;
            from++,to++;
            add(from+n,to,1,cost);
        }
        for(int i=2;i<=n-1;i++) add(i,i+n,1,0);
        add(s,n+1,2,0);add(n,t,2,0);
        while(maxflow>0&&spfa()) update();
        if(maxflow>0) cout<<"Not possible"<<endl;
        else cout<<ans<<endl;
    }
    return 0;
}
#endif
#ifdef method_2
/*

*/

#endif
#ifdef method_3
/*

*/

#endif

POJ 2195: Going Home
题解链接://www.greatytc.com/p/4d3f099b0254
POJ 3422: Kaka's Matrix Travels
经典的K取方格数,蓝书上有,这里不赘述了。
代码如下

/*

*/
#define method_1
#ifdef method_1
/*

*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=5000+5;
const int maxm=200000+5;
const int INF=0x3f3f3f3f;
struct node{
    int from,to,cap,cost;
}edge[maxm];
int n,k,tot=1,s,t,maxflow=0,ans=0,d[maxn],v[maxn],incf[maxn],pre[maxn],head[maxn];
void add(int from,int to,int cap,int cost){
    edge[++tot].to=to;
    edge[tot].cost=cost;
    edge[tot].cap=cap;
    edge[tot].from=head[from];
    head[from]=tot;
    edge[++tot].to=from;
    edge[tot].cost=-cost;
    edge[tot].cap=0;
    edge[tot].from=head[to];
    head[to]=tot;
}
int num(int i,int j,int k){ //k==0 表示入点 k==1 表示出点 
    return (i-1)*n+j+k*n*n;
}
bool spfa(){
    queue<int>q;
    memset(d,0xcf,sizeof(d));
    memset(v,0,sizeof(v));
    q.push(s);
    d[s]=0;
    v[s]=1;
    incf[s]=INF; //增广路上各边的最小剩余容量
    while(!q.empty()){
        int x=q.front();
        q.pop();
        v[x]=0;
        for(int i=head[x];i;i=edge[i].from){
            int y=edge[i].to;
            int cap=edge[i].cap;
            int cost=edge[i].cost;
            if(cap){
                if(d[y]<d[x]+cost){
                    d[y]=d[x]+cost;
                    incf[y]=min(incf[x],cap);
                    pre[y]=i;
                    if(!v[y]){
                        q.push(y);
                        v[y]=1;
                    }
                }
            }
        }
    }
    if(d[t]==0xcfcfcfcf) return false;
    return true; 
}
void update(){
    int x=t;
    while(x!=s){
        int i=pre[x];
        edge[i].cap-=incf[t];
        edge[i^1].cap+=incf[t];
        x=edge[i^1].to; //注意这里是i^1 不是i 
    }
    maxflow+=incf[t];
    ans+=d[t]*incf[t];
}
int main() {
    ios::sync_with_stdio(false);
    //freopen("K取方格数.in","r",stdin);
    cin>>n>>k;
    s=1,t=2*n*n;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            int c;
            cin>>c;
            add(num(i,j,0),num(i,j,1),1,c);
            add(num(i,j,0),num(i,j,1),k-1,0);
            if(j<n) add(num(i,j,1),num(i,j+1,0),k,0);
            if(i<n) add(num(i,j,1),num(i+1,j,0),k,0);
        }
    }
    while(spfa()) update();
    cout<<ans;
    return 0;
}
#endif
#ifdef method_2
/*

*/

#endif
#ifdef method_3
/*

*/

#endif
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容