网络流建模小结

连刷两周的网络流,对建模也有一定的认识。做此小结。

最大流

最大流可以说是网络流算法中比较简单的了,基本都是靠一条流来表示一种实际方案,常用于求方案数,最大值,可行性等。

POJ 3281 Dining

题目链接戳戳

题目大意:有一些牛,一些饮料和一些食品。饮料和食品都只有一种。每个牛有自己喜欢的食品和饮料。问能否合理分配使得每个牛的食品和饮料都是自己喜欢的。

建模思路:平常的网络流的模型是源点连一种物品,汇点连另一种物品做匹配,然而这个题一群牛对应着两种物品,该怎么办呢?记起一条s-t流可以表示一种方案,我们将饮料和源点相连,食物和汇点相连,牛放在中间和他的喜好相连直接跑最大流,若最大流等于牛的个数,就存在这么一种方案。另外要对牛拆点,避免一个牛匹配了两种饮料的问题。

代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstdlib>
#include<map>
#include<vector>
#include<ctime>
#include<stack>
#define mp make_pair
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define pb push_back
#define ll long long
#define ull unsigned long long

using namespace std;

inline ll read()
{
    long long f=1,sum=0;
    char c=getchar();
    while (c<'0' || c>'9')
    {
        if (c=='-') f=-1;
        c=getchar();
    }
    while (c>='0' && c<='9')
    {
        sum=sum*10+c-'0';
        c=getchar();
    }
    return sum*f;
}
const int MAXN=510;
const int MAXM=100010;
struct edge
{
    int next,to,val;
};
edge e[MAXM];
int head[MAXN],cnt=1;
void addedge(int u,int v,int w)
{
    e[++cnt].next=head[u];
    e[cnt].to=v;
    e[cnt].val=w;
    head[u]=cnt;
}
int level[MAXN],s,t;
queue <int> q;
bool bfs()
{
    memset(level,-1,sizeof(level));
    q.push(s);
    level[s]=1;
    while (!q.empty())
    {
        int x=q.front();
        q.pop();
        for (int i=head[x];i;i=e[i].next)
        {
            int v=e[i].to;
            if (level[v]!=-1 || e[i].val<=0)
                continue;
            level[v]=level[x]+1;
            q.push(v);
        }
    }
    if (level[t]==-1) return false;
    return true;
}
int dfs(int x,int f)
{
    if (x==t) return f;
    int w,tot=0;
    for (int i=head[x];i;i=e[i].next)
    {
        int v=e[i].to;
        if (level[v]!=level[x]+1 || e[i].val<=0)
            continue;
        w=dfs(v,min(e[i].val,f-tot));
        e[i].val-=w;
        e[i^1].val+=w;
        tot+=w;
        if (tot==f) return f;
    }
    if (f-tot) level[x]=-1;
    return tot;
}
int max_flow()
{
    int ans=0;
    while (bfs())
        ans+=dfs(s,INF);
    return ans;
}
int main()
{
    int n,n1,n2;
    scanf("%d%d%d",&n,&n1,&n2);
    s=0,t=2*n+n1+n2+1;
    for (int i=1;i<=n1;i++)
        addedge(s,i,1),addedge(i,s,0);
    for (int i=1;i<=n2;i++)
        addedge(i+n1,t,1),addedge(t,i+n1,0);
    int tmp=n1+n2;
    for (int i=1;i<=n;i++)
    {
        int num1,num2;
        addedge(i+tmp,i+tmp+n,1),addedge(i+tmp+n,i+tmp,0);
        scanf("%d%d",&num1,&num2);
        for (int j=1;j<=num1;j++)
        {
            int x;
            scanf("%d",&x);
            addedge(x,i+tmp,1);
            addedge(i+tmp,x,0);
        }
        for (int j=1;j<=num2;j++)
        {
            int x;
            scanf("%d",&x);
            addedge(i+tmp+n,x+n1,1);
            addedge(x+n1,i+tmp+n,0);
        }
    }
    int ans=max_flow();
    cout<<ans;
    return 0;
}

SGU 438 The Glorious Karlutka River =)

链接君:戳戳呗

题目大意:有一条东西向流淌的河,宽为 W,河中有 N 块石头,每块石头的坐标(Xi, Yi)和最大承受人数 Ci 已知。现在有 M 个游客在河的南岸,他们想穿越这条河流,但是每个人每次最远只能跳 D 米,每跳一次耗时 1 秒。问他们能否全部穿越这条河流,如果能,最少需要多长时间。

建模思路:传说中的“动态流问题”,在流量限制之外又添加了时间限制。对每个石头的每个时间点进行拆点,再对每个石头拆成入点和出点,两岸分别作为源汇,枚举时间加点,每次在上一秒的残余网络上继续最大流即可,看什么时候流量大于等于总人数即可。
我们可以发现总时间不会超过n+m,极端情况:所有石头排成一列,所有人一个接一个的跳过去。所以枚举时间只需要枚举n+m即可。

代码:

#include<iostream>  
#include<cstdio>  
#include<cstring>  
#include<algorithm>  
#include<cmath>  
#include<queue>  
using namespace std;  

const int MAXN = 42000;  
const int MAXM = 9600000;  
const int INF = 0x3f3f3f3f;  

struct Edge  
{  
    int from, to, cap, next;  
};  

struct Point  
{  
    double x, y;  
    int cap;  
};  

Edge edge[MAXM];  
Edge con[MAXN];  
Point p[MAXN];  
int head[MAXN];  
int headcon[MAXN];  
int level[MAXN];  
int src, des, cnt, cnt1, ans;  

double dist( Point a, Point b )  
{  
    return sqrt( (a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y) );  
}  

void addcon( int from, int to )  
{  
    con[cnt1].from = from;  
    con[cnt1].to = to;  
    con[cnt1].next = headcon[from];  
    headcon[from] = cnt1++;  

    swap( from, to );  

    con[cnt1].from = from;  
    con[cnt1].to = to;  
    con[cnt1].next = headcon[from];  
    headcon[from] = cnt1++;  
}  


void addedge( int from, int to, int cap )  
{  
    edge[cnt].from = from;  
    edge[cnt].to = to;  
    edge[cnt].cap = cap;  
    edge[cnt].next = head[from];  
    head[from] = cnt++;  

    swap( from, to );  

    edge[cnt].from = from;  
    edge[cnt].to = to;  
    edge[cnt].cap = 0;  
    edge[cnt].next = head[from];  
    head[from] = cnt++;  
}  

int bfs( )  
{  
    memset( level, -1, sizeof level );  
    queue<int> q;  
    while (!q.empty( ))  
        q.pop( );  

    level[src] = 0;  
    q.push( src );  

    while (!q.empty( ))  
    {  
        int u = q.front( );  
        q.pop( );  

        for (int i = head[u]; i != -1; i = edge[i].next)  
        {  
            int v = edge[i].to;  
            if (edge[i].cap > 0 && level[v] == -1)  
            {  
                level[v] = level[u] + 1;  
                q.push( v );  
            }  
        }  
    }  
    return level[des] != -1;  
}  

int dfs( int u, int f )  
{  
    if (u == des)   return f;  
    int tem;  
    for (int i = head[u]; i != -1; i = edge[i].next)  
    {  
        int v = edge[i].to;  
        if (edge[i].cap > 0 && level[v] == level[u] + 1)  
        {  
            tem = dfs( v, min( f, edge[i].cap ) );  
            if (tem > 0)  
            {  
                edge[i].cap -= tem;  
                edge[i ^ 1].cap += tem;  
                return tem;  
            }  
        }  
    }  
    level[u] = -1;  
    return 0;  
}  

int Dinic( )  
{  
    int ans = 0, tem;  
    while (bfs( ))  
    {  
        while ((tem = dfs( src, INF )) > 0)  
        {  
            ans += tem;  
        }  
    }  
    return ans;  
}  

int main( )  
{  
    int n, m, d, w;  
    src = 0;  
    des = 5500;  

    while (cin >> n >> m >> d >> w)  
    {  
        memset( head, -1, sizeof head );  
        memset( headcon, -1, sizeof headcon );  
        ans = cnt = cnt1 = 0;  

        for (int i = 1; i <= n; i++)  
        {  
            cin >> p[i].x >> p[i].y >> p[i].cap;  
        }  

        if (d >= w)  
        {  
            cout << 1 << endl;  
            continue;  
        }  

        for (int i = 1; i <= n; i++)  
        {  
            if (p[i].y <= d)  
                addcon( src, i );  
            if (w - p[i].y <= d)  
                addcon( i, des );  

            for (int j = i + 1; j <= n; j++)  
            {  
                if (dist( p[i], p[j] ) <= d)  
                    addcon( i, j );  
            }  
            addcon( i, i );  
        }  


        int t;  
        for (t = 1; t <= n + m; t++) // time   
        {  

            for (int j = headcon[src]; j != -1; j = con[j].next)  
            {  
                int v = con[j].to;  
                addedge( src, t * 200 + v, INF );  
            }  


            for (int k = 1; k <= n; k++) //rocks  
            {  
                for (int j = headcon[k]; j != -1; j = con[j].next)  
                {  
                    int v = con[j].to;  
                    if (v == des)  
                        addedge( (t - 1) * 200 + k + 50, des, INF );  
                    else  
                        addedge( (t - 1) * 200 + k + 50, t * 200 + v, INF );  
                }  
                addedge( t * 200 + k, t * 200 + k + 50, p[k].cap );  
            }  

            int temp = Dinic( );  
            ans += temp;  
            if (ans >= m) break;  
        }  
        if (t > n + m)  
            cout << "IMPOSSIBLE" << endl;  
        else  
            cout << t << endl;  
    }  
    return 0;  
} 

SPOJ 287 Smart Network Administrator

链接君

题目大意:一座村庄有 N 户人家。只有第一家可以连上互联网,其他人家要想上网必须拉一根缆线通过若干条街道连到第一家。每一根完整的缆线只能有一种颜色。网管有一个要求,各条街道内不同人家的缆线必须不同色,且总的不同颜色种数最小。求在指定的 K 户人家都能上网的前提下,最小的不同颜色种数。

建模思路:先用普通的建图方法:以第一家作为T,K户每个人作为一个点连接(s,i,1),每条街道连边(u,v,INF)和(v,u,INF),用网络流s-t流代表方案的思想,这里的一条流代表一条缆线。
是不是看上去非常的优秀!然而是错的。我们最大流只能使总流量最大,不能保证支流也满足要求。
这时,我们强制街道的流量不是INF,改成一个定值w,此时去跑最大流,若最大流等于K则说明这种方案符合要求,若小于K就说明流量过小。
是不是想到了二分!更加优秀了。
网络流经常与二分结合的qwqqq。

#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstdlib>
#include<ctime>
#include<map>
#include<queue>
#include<vector>
#include<stack>
#include<set>
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define mp make_pair
#define ll long long
#define ull unsigned long long
#define pb push_back

using namespace std;

inline ll read()
{
    long long f=1,sum=0;
    char c=getchar();
    while (c<'0' || c>'9')
    {
        if (c=='-') f=-1;
        c=getchar();
    }
    while (c>='0' && c<='9')
    {
        sum=sum*10+c-'0';
        c=getchar();
    }
    return sum*f;
}
const int MAXN=1010;
const int MAXM=100010;
struct edge
{
    int next,to,val;
};
edge e[MAXM];
int head[MAXN],cnt=1;
void addedge(int u,int v,int w)
{
    e[++cnt].next=head[u];
    e[cnt].to=v;
    e[cnt].val=w;
    head[u]=cnt;
}
int level[MAXN],s,t;
queue <int> q;
bool bfs()
{
    memset(level,-1,sizeof(level));
    level[s]=1;
    q.push(s);
    while (!q.empty())
    {
        int x=q.front();
        q.pop();
        for (int i=head[x];i;i=e[i].next)
        {
            int v=e[i].to;
            if (level[v]!=-1 || e[i].val<=0)
                continue;
            level[v]=level[x]+1;
            q.push(v);
        }
    }
    if (level[t]==-1) return false;
    return true;
}
int dfs(int x,int f)
{
    if (x==t) return f;
    int w,tot=0;
    for (int i=head[x];i;i=e[i].next)
    {
        int v=e[i].to;
        if (level[v]!=level[x]+1 || e[i].val<=0)
            continue;
        w=dfs(v,min(e[i].val,f-tot));
        e[i].val-=w;
        e[i^1].val+=w;
        tot+=w;
        if (tot==f) return f;
    }
    if (f-tot) level[x]=-1;
    return tot;
}
int max_flow()
{
    int ans=0;
    while (bfs())
        ans+=dfs(s,INF);
    return ans;
}
int u[MAXN],v[MAXN];
int a[MAXN];
int n,m,k;
bool check(int mid)
{
    memset(head,0,sizeof(head));
    cnt=1;
    for (int i=1;i<=k;i++)
        addedge(s,a[i],1),addedge(a[i],s,0);
    for (int i=1;i<=m;i++)
        addedge(u[i],v[i],mid),addedge(v[i],u[i],mid);
    int flow=max_flow();
    if (flow==k) return true;
    return false;
}
int main()
{
    int T;
    scanf("%d",&T);
    while (T--)
    {
        scanf("%d%d%d",&n,&m,&k);
        for (int i=1;i<=k;i++)
            scanf("%d",&a[i]);
        for (int i=1;i<=m;i++)
            scanf("%d%d",&u[i],&v[i]);
        int l=1,r=n,mid,ans;
        s=0,t=1;
        while (l<=r)
        {
            mid=(l+r)>>1;
            if (check(mid))
                r=mid-1,ans=mid;
            else
                l=mid+1;
        }
        cout<<ans<<endl;
    }
    return 0;
}

最小割

最小割虽然算法和最大流是一样的,但是建模更加难想,可以解决的问题也更加丰富多样。

HOJ 2634 How to earn more

链链链链链接
题目大意:有 M 个项目和 N 个员工。做项目 i 可以获得 Ai 元,但是必须雇用若干个指定的员工。雇用员工 j 需要花费 Bj 元,且一旦雇用,员工 j 可以参加多个项目的开发。问经过合理的项目取舍,最多能挣多少钱。

建模思路:传说中的“蕴含式最大获利问题”,是最大权闭合子图模型的一种。
最大权闭合子图的建模方法:所有收益与源点连边,所有支出与汇点连边,收益和支出的依赖关系相互连边,边权和减去最小割即可。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstdlib>
#include<map>
#include<vector>
#include<ctime>
#include<stack>
#define mp make_pair
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define pb push_back
#define ll long long
#define ull unsigned long long

using namespace std;

inline ll read()
{
    long long f=1,sum=0;
    char c=getchar();
    while (c<'0' || c>'9')
    {
        if (c=='-') f=-1;
        c=getchar();
    }
    while (c>='0' && c<='9')
    {
        sum=sum*10+c-'0';
        c=getchar();
    }
    return sum*f;
}
const int MAXN=310;
const int MAXM=100010;
struct edge
{
    int next,to,val;
};
edge e[MAXM];
int head[MAXN],cnt=1;
void addedge(int u,int v,int w)
{
    e[++cnt].next=head[u];
    e[cnt].to=v;
    e[cnt].val=w;
    head[u]=cnt;
}
int level[MAXN],s,t;
queue <int> q;
bool bfs()
{
    memset(level,-1,sizeof(level));
    q.push(s);
    level[s]=1;
    while (!q.empty())
    {
        int x=q.front();
        q.pop();
        for (int i=head[x];i;i=e[i].next)
        {
            int v=e[i].to;
            if (level[v]!=-1 || e[i].val<=0)
                continue;
            level[v]=level[x]+1;
            q.push(v);
        }
    }
    if (level[t]==-1) return false;
    return true;
}
int dfs(int x,int f)
{
    if (x==t) return f;
    int w,tot=0;
    for (int i=head[x];i;i=e[i].next)
    {
        int v=e[i].to;
        if (level[v]!=level[x]+1 || e[i].val<=0)
            continue;
        w=dfs(v,min(e[i].val,f-tot));
        e[i].val-=w;
        e[i^1].val+=w;
        tot+=w;
        if (tot==f) return f;
    }
    if (f-tot) level[x]=-1;
    return tot;
}
int max_flow()
{
    int ans=0;
    while (bfs())
        ans+=dfs(s,INF);
    return ans;
}
int main()
{
    int T;
    scanf("%d",&T);
    while (T--)
    {
        memset(head,0,sizeof(head));
        cnt=1;
        int n,m;
        scanf("%d%d",&n,&m);
        s=0,t=n+m+10;
        int ans=0;
        for (int i=1;i<=n;i++)
        {
            int x;
            scanf("%d",&x);
            addedge(s,i,x),addedge(i,s,0);
            ans+=x;
        }
        for (int i=1;i<=m;i++)
        {
            int x;
            scanf("%d",&x);
            addedge(i+n,t,x),addedge(t,i+n,0);
        }
        for (int i=1;i<=n;i++)
        {
            int num;
            scanf("%d",&num);
            for (int j=1;j<=num;j++)
            {
                int x;
                scanf("%d",&x);
                x++;
                addedge(i,x+n,INF),addedge(x+n,i,0);
            }
        }
        int flow=max_flow();
        ans-=flow;
        printf("%d\n",ans);
    }
    return 0;
}

POJ 1815 Friendship

链接接接接接
题目大意:现代社会人们都靠电话通信。A 与 B 能通信当且仅当 A 知道 B 的电话号或者 A知道 C 的电话号且 C 与 B 能通信。若 A 知道 B 的电话号,那么 B 也知道 A 的电话号。然而不好的事情总是会发生在某些人身上,比如他的电话本丢了,同时他又换了电话号,导致他跟所有人失去了联系。现在给定 N 个人之间的通信关系以及特定的两个人 S 和 T,问最少几个人发生不好的事情可以导致 S 与 T 无法通信并输出这些人。如果存在多组解,输出字典序最小的一组。

思路:求的是一个最小点割集,我们拆点构图:一个人i拆成(i,i’,1),对于每一个关系,连边(i’,j,INF),(j’,i,INF),流量设成无穷可以保证这条边不会被割掉,只会去割拆点后的人(带表这个人发生了不好的事)。
那如何输出字典序最小的方案呢?
我们先求出来最小割,然后编号从小到大枚举删点。如果删去这个点对答案有影响,那么这个点应该是删掉了,从小到大枚举做到了字典序最小。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstdlib>
#include<map>
#include<vector>
#include<ctime>
#include<stack>
#define mp make_pair
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define pb push_back
#define ll long long
#define ull unsigned long long

using namespace std;

inline ll read()
{
    long long f=1,sum=0;
    char c=getchar();
    while (c<'0' || c>'9')
    {
        if (c=='-') f=-1;
        c=getchar();
    }
    while (c>='0' && c<='9')
    {
        sum=sum*10+c-'0';
        c=getchar();
    }
    return sum*f;
}
const int MAXN=2010;
const int MAXM=2000010;
struct edge
{
    int next,to,val;
};
edge e[MAXM];
int head[MAXN],cnt=1;
void addedge(int u,int v,int w)
{
    e[++cnt].next=head[u];
    e[cnt].to=v;
    e[cnt].val=w;
    head[u]=cnt;
}
int level[MAXN],s,t,n;
queue <int> q;
bool bfs()
{
    memset(level,-1,sizeof(level));
    level[s]=1;
    q.push(s);
    while (!q.empty())
    {
        int x=q.front();
        q.pop();
        for (int i=head[x];i;i=e[i].next)
        {
            int v=e[i].to;
            if (level[v]!=-1 || e[i].val<=0)
                continue;
            level[v]=level[x]+1;
            q.push(v);
        }
    }
    if (level[t]==-1) return false;
    return true;
}
int dfs(int x,int f)
{
    if (x==t) return f;
    int w,tot=0;
    for (int i=head[x];i;i=e[i].next)
    {
        int v=e[i].to;
        if (level[v]!=level[x]+1 || e[i].val<=0)
            continue;
        w=dfs(v,min(e[i].val,f-tot));
        e[i].val-=w;
        e[i^1].val+=w;
        tot+=w;
        if (tot==f) return f;
    }
    if (f-tot) level[x]=-1;
    return tot;
}
int max_flow()
{
    int ans=0;
    while (bfs())
        ans+=dfs(s,INF);
    return ans;
}
int a[MAXN][MAXN];
bool vis[MAXN];
void build()
{
    memset(head,0,sizeof(head));
    cnt=1;
    for (int i=1;i<=n;i++)
        if (!vis[i])
            addedge(i,i+n,1),addedge(i+n,i,0);
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++)
            if (a[i][j])
                addedge(i+n,j,INF),addedge(j,i+n,0);
}
vector <int> anss;
int main()
{
    scanf("%d%d%d",&n,&s,&t);
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++)
            scanf("%d",&a[i][j]);
    if (a[s][t])
    {
        printf("NO ANSWER!");
        return 0;
    }
    s+=n;
    build();
    int flow=max_flow();
    printf("%d\n",flow);
    if (!flow) return 0;
    for (int i=1;i<=n;i++)
    {
        if (i==s-n || i==t) continue;
        vis[i]=true;
        build();
        int tmp=max_flow();
        if (tmp<flow)
            flow--,anss.push_back(i);
        else
            vis[i]=false;
        if (!flow) break;
    }
    for (int i=0;i<(int)anss.size()-1;i++)
        printf("%d ",anss[i]);
    printf("%d",anss[(int)anss.size()-1]);
    return 0;
}

SPOJ 839 Optimal Marks

正经的链接

题目大意:给出一个无向图,每个点有一个标号 mark[i],不同点可能有相同的标号。对于
一条边(u, v),它的权值定义为 mark[u] xor mark[v]。现在一些点的标号已定,请
决定剩下点的标号,使得总的边权和最小。

思路:此题出自胡伯涛的论文《最小割模型在信息学竞赛中的应用》。我们可以发现,对于所有的二进制位,他们是互不影响的。所以对于每一个二进制位建图,分别跑最小割。它就变成了一个“二者选其一”式的问题。对于所有标号为1的与s相连,标号为0的与汇点相连,待定的都设为0,有边的01连边,最小割后在残余网络上顺着源点dfs,所有能走到的地方这一位都是1,标记一下即可。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstdlib>
#include<map>
#include<vector>
#include<ctime>
#include<stack>
#define mp make_pair
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define pb push_back
#define ll long long
#define ull unsigned long long

using namespace std;

inline ll read()
{
    long long f=1,sum=0;
    char c=getchar();
    while (c<'0' || c>'9')
    {
        if (c=='-') f=-1;
        c=getchar();
    }
    while (c>='0' && c<='9')
    {
        sum=sum*10+c-'0';
        c=getchar();
    }
    return sum*f;
}
const int MAXN=20010;
const int MAXM=1000010;
struct edge
{
    int next,to,val;
};
edge e[MAXM];
int head[MAXN],cnt=1;
void addedge(int u,int v,int w)
{
    e[++cnt].next=head[u];
    e[cnt].to=v;
    e[cnt].val=w;
    head[u]=cnt;
}
int level[MAXN],s,t;
queue <int> q;
bool bfs()
{
    memset(level,-1,sizeof(level));
    q.push(s);
    level[s]=1;
    while (!q.empty())
    {
        int x=q.front();
        q.pop();
        for (int i=head[x];i;i=e[i].next)
        {
            int v=e[i].to;
            if (level[v]!=-1 || e[i].val<=0)
                continue;
            level[v]=level[x]+1;
            q.push(v);
        }
    }
    if (level[t]==-1) return false;
    return true;
}
int dfs(int x,int f)
{
    if (x==t) return f;
    int w,tot=0;
    for (int i=head[x];i;i=e[i].next)
    {
        int v=e[i].to;
        if (level[v]!=level[x]+1 || e[i].val<=0)
            continue;
        w=dfs(v,min(e[i].val,f-tot));
        e[i].val-=w;
        e[i^1].val+=w;
        tot+=w;
        if (tot==f) return f;
    }
    if (f-tot) level[x]=-1;
    return tot;
}
int max_flow()
{
    int ans=0;
    while (bfs())
        ans+=dfs(s,INF);
    return ans;
}
int a[510][510],n,num[510];
void build(int bit)
{
    for (int i=1;i<=n;i++)
    {
        if (!num[i]) continue;
        if (num[i]&(1<<bit))
            addedge(s,i,INF),addedge(i,s,0);
        else
            addedge(i,t,INF),addedge(t,i,0);
    }
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++)
            if (a[i][j])
                addedge(i,j,1),addedge(j,i,0);
}
int ans[MAXN];
bool visit[MAXN];
void dfs1(int x,int bit)
{
    visit[x]=1;
    ans[x]|=(1<<bit);
    for (int i=head[x];i;i=e[i].next)
    {
        int v=e[i].to;
        if (e[i].val<=0 || visit[v]) continue;
        dfs1(v,bit);
    }
}
void init()
{
    memset(head,0,sizeof(head));
    memset(visit,0,sizeof(visit));
    cnt=1;
}
int main()
{
    int T;
    scanf("%d",&T);
    while (T--)
    {
        memset(num,0,sizeof(num));
        memset(ans,0,sizeof(ans));
        memset(a,0,sizeof(a));
        int m;
        scanf("%d%d",&n,&m);
        s=0,t=n+10;
        for (int i=1;i<=m;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            a[u][v]=a[v][u]=1;
        }
        int k;
        scanf("%d",&k);
        for (int i=1;i<=k;i++)
        {
            int id,x;
            scanf("%d%d",&id,&x);
            num[id]=x;
        }
        for (int i=0;i<=31;i++)
        {
            init();
            build(i);
            max_flow();
            dfs1(s,i);
        }
        for (int i=1;i<=n;i++)
            printf("%d\n",ans[i]);
    }
    return 0;
}

SPOJ 1693 Coconuts

链接

题目大意:有趣的题干。。N个城堡守卫正在为非洲的燕子能否搬运椰子而进行投票。每个人都可以持赞成或者反对。每个人都有几个朋友,他不希望和他的朋友的票是不一样的,所以每个人可以投出“违心”的票,问每个人在经过抉择后,违心的票数和持不同意见的朋友对数的和最少。

思路:又是一个“二者选其一”的问题,非常经典的建图。所有投赞成的守卫连边(s,i,1),反对票连边(i,t,1),代表投赞成票和反对票的人改变自己意愿的花费都是1,再对所有朋友连边(i,j,1),(j,i,1),代表若两人分属不同观点(一个连接s一个连接t),花费是1,做出最小割即为答案。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstdlib>
#include<map>
#include<vector>
#include<ctime>
#include<stack>
#define mp make_pair
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define pb push_back
#define ll long long
#define ull unsigned long long

using namespace std;

inline ll read()
{
    long long f=1,sum=0;
    char c=getchar();
    while (c<'0' || c>'9')
    {
        if (c=='-') f=-1;
        c=getchar();
    }
    while (c>='0' && c<='9')
    {
        sum=sum*10+c-'0';
        c=getchar();
    }
    return sum*f;
}
const int MAXN=510;
const int MAXM=1000010;
struct edge
{
    int next,to,val;
};
edge e[MAXM];
int head[MAXN],cnt=1;
void addedge(int u,int v,int w)
{
    e[++cnt].next=head[u];
    e[cnt].to=v;
    e[cnt].val=w;
    head[u]=cnt;
}
int level[MAXN],s,t;
queue <int> q;
bool bfs()
{
    memset(level,-1,sizeof(level));
    q.push(s);
    level[s]=1;
    while (!q.empty())
    {
        int x=q.front();
        q.pop();
        for (int i=head[x];i;i=e[i].next)
        {
            int v=e[i].to;
            if (level[v]!=-1 || e[i].val<=0)
                continue;
            level[v]=level[x]+1;
            q.push(v);
        }
    }
    if (level[t]==-1) return false;
    return true;
}
int dfs(int x,int f)
{
    if (x==t) return f;
    int w,tot=0;
    for (int i=head[x];i;i=e[i].next)
    {
        int v=e[i].to;
        if (level[v]!=level[x]+1 || e[i].val<=0)
            continue;
        w=dfs(v,min(e[i].val,f-tot));
        e[i].val-=w;
        e[i^1].val+=w;
        tot+=w;
        if (tot==f) return f;
    }
    if (f-tot) level[x]=-1;
    return tot;
}
int max_flow()
{
    int ans=0;
    while (bfs())
        ans+=dfs(s,INF);
    return ans;
}
int main()
{
    while (1)
    {
        memset(head,0,sizeof(head));
        cnt=1;
        int n,m;
        scanf("%d%d",&n,&m);
        s=0,t=n+1;
        if (n+m==0) break;
        for (int i=1;i<=n;i++)
        {
            int x;
            scanf("%d",&x);
            if (x) addedge(s,i,1),addedge(i,s,0);
            else addedge(i,t,1),addedge(t,i,0);
        }
        for (int i=1;i<=m;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            addedge(u,v,1),addedge(v,u,0);
            addedge(v,u,1),addedge(u,v,0);
        }
        int cut=max_flow();
        printf("%d\n",cut);
    }
    return 0;
}

Codeforces GYM 100729F

链接

题目大意:你有一个n*n的网格,每个网格可以是坑或土,有一个人请你把这块地修建成一个“pool”。你有如下操作:1.把这块地保持原来的样子,无花费。2.把土挖掉变成坑,花费d。3.把坑填上土,花费f。4.在土和坑的边界加上一层边界,花费b。在最后将会在所有的坑中填上水,成为一个pool,所以整个网格最外面的一圈必须是土,求使这块地变成一个pool的花费最小(坑的面积可以是0)。

思路:第一直觉,这是一个最小割,简直是coconut的翻版,但是往模型上一套,发现貌似有点问题:那道题的花费都是1,这里有三个不同的花费,怎么代表选或者不选呢。我一转念就把它放弃了。。。。
转投我的第二直觉,既然是费用,那就费用流吧。写啊写啊写啊写。。。发现没法限制,拆点!还有问题,继续拆!结果我一个点拆了六个点去跑,依然有问题。
回来继续想。又看到了edelweiss大神的《网络流建模汇总》中的一段话

二者取其一式问题:个人认为这是最小割次 NB 的应用。这类建模方法也有一个明显的特点,就是每个点都可以有两种方案供选择,每种方案都有一个花费,必须且只能选择其中一种;另外如果某两个点选择了不同的方案,还要在这两个点之间增加额外的费用。这种应用其实也可算作第一种构图法,只不过它的特点更明显,所以我把它单列出来。

再回去想这个题,与这个模型完美的契合。这就是coconut那个题的建模方法。
建图方法就一目了然了:所有坑与s连边(s,i,f),土与汇点连边(i,t,d),四联通的点连边(i,j,b),(j,i,b),最外围的一圈因为必须是土,所以提前算出,然后最小割。结束。

#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstdlib>
#include<ctime>
#include<map>
#include<queue>
#include<vector>
#include<stack>
#include<set>
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define mp make_pair
#define ll long long
#define ull unsigned long long
#define pb push_back

using namespace std;

inline ll read()
{
    long long f=1,sum=0;
    char c=getchar();
    while (c<'0' || c>'9')
    {
        if (c=='-') f=-1;
        c=getchar();
    }
    while (c>='0' && c<='9')
    {
        sum=sum*10+c-'0';
        c=getchar();
    }
    return sum*f;
}
const int MAXN=3010;
const int MAXM=100010;
struct edge
{
    int next,to,val; 
};
edge e[MAXM];
int head[MAXN],cnt=1;
void addedge(int u,int v,int w)
{
    e[++cnt].next=head[u];
    e[cnt].to=v;
    e[cnt].val=w;
    head[u]=cnt;
    swap(u,v);
    e[++cnt].next=head[u];
    e[cnt].to=v;
    e[cnt].val=0;
    head[u]=cnt;
}
int level[MAXN],s,t;
queue <int> q;
bool bfs()
{
    memset(level,-1,sizeof(level));
    q.push(s);
    level[s]=1;
    while (!q.empty())
    {
        int x=q.front();
        q.pop();
        for (int i=head[x];i;i=e[i].next)
        {
            int v=e[i].to;
            if (level[v]!=-1 || e[i].val<=0)
                continue;
            level[v]=level[x]+1;
            q.push(v);
        }
    }
    if (level[t]==-1) return false;
    return true;
}
int dfs(int x,int f)
{
    if (x==t) return f;
    int w,tot=0;
    for (int i=head[x];i;i=e[i].next)
    {
        int v=e[i].to;
        if (level[v]!=level[x]+1 || e[i].val<=0)
            continue;
        w=dfs(v,min(e[i].val,f-tot));
        e[i].val-=w;
        e[i^1].val+=w;
        tot+=w;
        if (tot==f) return f;
    }
    if (f-tot) level[x]=-1;
    return tot;
}
int max_flow()
{
    int ans=0;
    while (bfs())
        ans+=dfs(s,INF);
    return ans;
}
char c[55][55];
int n,m;
int P(int x,int y)
{
    return (x-1)*m+y;
}
void init()
{
    memset(head,0,sizeof(head));
    cnt=1;
}
const int dx[4]={-1,0,1,0};
const int dy[4]={0,1,0,-1};
int main()
{
    int T;
    scanf("%d",&T);
    while (T--)
    {
        init();
        int d,b,f;
        scanf("%d%d",&m,&n);
        scanf("%d%d%d",&d,&f,&b);
        for (int i=1;i<=n;i++)
            scanf("%s",c[i]+1);
        int tot=0;
        s=0,t=MAXN-10;
        for (int i=1;i<=n;i++)
            for (int j=1;j<=m;j++)
            {
                int p=P(i,j);
                if (i==1 || j==1 || i==n || j==m)
                    tot+=(c[i][j]=='.')*f,addedge(s,p,INF);
                else if (c[i][j]=='.')
                    addedge(p,t,f);
                else 
                    addedge(s,p,d);
                for (int k=0;k<4;k++)
                {
                    int nx=i+dx[k],ny=j+dy[k];
                    int np=P(nx,ny);
                    if (nx<=0 || nx>n || ny<=0 || ny>m)
                        continue;
                    addedge(p,np,b);
                }
            }
        tot+=max_flow();
        cout<<tot<<endl;
    }
    return 0;
}

上下界网络流

做的题不多,说一个。。。

POJ 2396 Budget

请出链接君
题目大意:一个 M*N 的矩阵,给定每行、每列的元素之和 Ri、Cj 以及 c 个满足下列形式的约束条件:i j {< = >} k(i, j 表示第 i 行第 j 列的元素,为 0 表示整列或整行),问是否存在满足所有约束条件的矩阵。

建模思路:有源汇上下界可行流问题。
1.把每一行看做一个点, 把每一列看做一个点。
2.建立一个源点s,连接s与每一行,容量上限下限设为该行和。
3.建立一个汇点t,连接每一列与t,容量上限下限设为该列和。
4.对于每一行跟每一列,先连一条下限为0,上限为无穷大的边,然后根据给出的条件修改边上下界。
这就是一个有源汇的上下界网络流,转化成无源汇的上下界网络流:从t向s连一条INF的边,使流量守恒即可。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstdlib>
#include<map>
#include<vector>
#include<ctime>
#include<stack>
#define mp make_pair
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define pb push_back
#define ll long long
#define ull unsigned long long

using namespace std;

inline ll read()
{
    long long f=1,sum=0;
    char c=getchar();
    while (c<'0' || c>'9')
    {
        if (c=='-') f=-1;
        c=getchar();
    }
    while (c>='0' && c<='9')
    {
        sum=sum*10+c-'0';
        c=getchar();
    }
    return sum*f;
}
const int MAXN=10010;
const int MAXM=1000010;
struct edge
{
    int next,to,val;
};
edge e[MAXM];
int head[MAXN],cnt=1;
void addedge(int u,int v,int w)
{
    e[++cnt].next=head[u];
    e[cnt].to=v;
    e[cnt].val=w;
    head[u]=cnt;
}
int level[MAXN],s,t,ss,tt;
queue <int> q;
bool bfs()
{
    memset(level,-1,sizeof(level));
    q.push(s);
    level[s]=1;
    while (!q.empty())
    {
        int x=q.front();
        q.pop();
        for (int i=head[x];i;i=e[i].next)
        {
            int v=e[i].to;
            if (level[v]!=-1 || e[i].val<=0)
                continue;
            level[v]=level[x]+1;
            q.push(v);
        }
    }
    if (level[t]==-1) return false;
    return true;
}
int dfs(int x,int f)
{
    if (x==t) return f;
    int w,tot=0;
    for (int i=head[x];i;i=e[i].next)
    {
        int v=e[i].to;
        if (level[v]!=level[x]+1 || e[i].val<=0)
            continue;
        w=dfs(v,min(e[i].val,f-tot));
        e[i].val-=w;
        e[i^1].val+=w;
        tot+=w;
        if (tot==f) return f;
    }
    if (f-tot) level[x]=-1;
    return tot;
}
int n,m,up[250][250],down[250][250],tot;
int id[250][250];
int max_flow()
{
    int ans=0;
    while (bfs())
        ans+=dfs(s,INF);
    return ans==tot;
}
void init()
{
    memset(head,0,sizeof(head));
    cnt=1;
    memset(down,0,sizeof(down));
    memset(up,inf,sizeof(up));
    memset(id,0,sizeof(id));
}
bool build()
{
    addedge(tt,ss,INF),addedge(ss,tt,0);
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++)
            if (down[i][j]>up[i][j]) return 0;
            else
            {
                addedge(i,n+j,up[i][j]-down[i][j]),addedge(n+j,i,0);
                tot+=down[i][j];
                id[i][j]=cnt-1;
                addedge(s,n+j,down[i][j]),addedge(n+j,s,0);
                addedge(i,t,down[i][j]),addedge(t,i,0);
            }
    return 1;
}
int main()
{
    int T;
    scanf("%d",&T);
    while (T--)
    {
        scanf("%d%d",&n,&m);
        s=0,t=n+m+10,ss=n+m+1,tt=n+2+m;
        init();
        int tot1=0,tot2=0;
        for (int i=1;i<=n;i++)
        {
            int x;
            scanf("%d",&x);
            tot1+=x;
            addedge(s,i,x),addedge(i,s,0);
            addedge(ss,t,x),addedge(t,ss,0);
        }
        for (int i=1;i<=m;i++)
        {
            int x;
            scanf("%d",&x);
            tot2+=x;
            addedge(s,tt,x),addedge(tt,s,0);
            addedge(i+n,t,x),addedge(t,i+n,0);
        }
        tot=tot1+tot2;
        int k;
        scanf("%d",&k);
        while (k--)
        {
            int x,y,num; char sss[5];
            scanf("%d%d%s%d",&x,&y,sss+1,&num);
            int x1,y1,x2,y2;
            x1=x2=x,y1=y2=y;
            if (!x) x1=1,x2=n;
            if (!y) y1=1,y2=m;
            for (int i=x1;i<=x2;i++)
                for (int j=y1;j<=y2;j++)
                    if (sss[1]=='=')
                        down[i][j]=max(down[i][j],num),up[i][j]=min(up[i][j],num);
                    else if (sss[1]=='>')
                        down[i][j]=max(down[i][j],num+1);
                    else
                        up[i][j]=min(up[i][j],num-1);
        }
        if (tot1!=tot2 || !build() || !max_flow())
            printf("IMPOSSIBLE\n");
        else
        {
            for (int i=1;i<=n;i++)
            {
                printf("%d",down[i][1]+e[id[i][1]^1].val);
                for (int j=2;j<=m;j++)
                    printf(" %d",down[i][j]+e[id[i][j]^1].val);
                putchar('\n');
            }
        }
        putchar('\n');
    }
    return 0;
}

费用流

费用流的模型较多,也更加的灵活,每个题都有自己的特点,列出几个比较独特的来剖析一下。

HOJ 2543 Stone IV

链接

题目大意:在无向图 G 中,wywcgs 要从源点 s 购买一些石头并运到汇点 t。每块石头单价是P 元。每条边 i 有一个初始容量 Ci,当容量超过 Ci 时,每增加一单位容量要额外花费 Ei 元。wywcgs 现在手头只有 C 元,问他最多能买多少块石头并成功运到目的地。

思路:经典的“凸费用流问题”——费用随着流的增大呈分段的线性状态。我们采用拆边的方式来解决这个问题:(u,v)拆成(u, v, Ci, 0), (u, v, INF, Ei),根据费用流原理,一定会先选择前一条边走,故拆边成功,跑普通费用流即可。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstdlib>
#include<map>
#include<vector>
#include<ctime>
#include<stack>
#define mp make_pair
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define pb push_back
#define ll long long
#define ull unsigned long long

using namespace std;

inline ll read()
{
    long long f=1,sum=0;
    char c=getchar();
    while (c<'0' || c>'9')
    {
        if (c=='-') f=-1;
        c=getchar();
    }
    while (c>='0' && c<='9')
    {
        sum=sum*10+c-'0';
        c=getchar();
    }
    return sum*f;
}
const int MAXN=10010;
const int MAXM=100010;
struct edge
{
    int next,to,val,cost,from;
};
edge e[MAXM];
int head[MAXN],cnt=1;
void addedge(int u,int v,int w,int cost)
{
    e[++cnt].next=head[u];
    e[cnt].to=v;
    e[cnt].val=w;
    e[cnt].cost=cost;
    e[cnt].from=u;
    head[u]=cnt;
}
queue <int> q;
bool inqueue[MAXN];
int dis[MAXN],from[MAXN],s,t;
bool spfa()
{
    memset(dis,inf,sizeof(dis));
    dis[s]=0,inqueue[s]=1;
    q.push(s);
    while (!q.empty())
    {
        int x=q.front();
        q.pop();
        for (int i=head[x];i;i=e[i].next)
        {
            int v=e[i].to;
            if (dis[v]>dis[x]+e[i].cost && e[i].val)
            {
                dis[v]=dis[x]+e[i].cost;
                from[v]=i;
                if (!inqueue[v])
                    q.push(v),inqueue[v]=1;
            }
        }
        inqueue[x]=0;
    }
    if (dis[t]<INF) return 1;
    return 0;
}
int main()
{
    int T;
    scanf("%d",&T);
    while (T--)
    {
        memset(head,0,sizeof(head));
        cnt=1;
        s=0,t=1;
        int n,m,tot,p;
        scanf("%d%d%d%d",&n,&m,&tot,&p);
        for (int i=1;i<=m;i++)
        {
            int u,v,w,c;
            scanf("%d%d%d%d",&u,&v,&w,&c);
            addedge(u,v,w,0);
            addedge(v,u,0,0);
            addedge(u,v,INF,c);
            addedge(v,u,0,-c);
        }
        int ans=0;
        while (spfa())
        {
            ll ans1=0;
            int flow=INF;
            for (int i=t;i!=s;i=e[from[i]].from)
                flow=min(flow,e[from[i]].val);
            for (int i=t;i!=s;i=e[from[i]].from)
            {
                e[from[i]].val-=flow;
                e[from[i]^1].val+=flow;
            }
            ans1+=flow*dis[t];
            if (tot>(ll)ans1+(ll)p*flow)
            {
                tot-=ans1+p*flow;
                ans+=flow;
            }
            else
            {
                ans+=tot/(dis[t]+p);
                break;
            }
        }
        printf("%d\n",ans);

    }
    return 0;
}

HOJ 2715 Matirx3

链接

题目大意:一个 N*N 的网格,每个单元都有一个价值 Vi 的宝物和一个高度 Hi。现在要作至多 K 次旅行,每次旅行如下:他可以借助的直升机飞到任意一个单元,之后他每次只能向相邻的且高度比当前所在格子低的格子移动。当他移动到一个边界的格子上时,他可以跳出这个网格并完成一次旅行。旅行中所到之处的宝物他可以全部拿走,一旦拿走原来的格子里就没有宝物了。问他最多能拿走价值多少的宝物。

思路:和上题一样,我们拆点的同时拆边,并加边(i,i’,1,-Vi), (i,i’,INF,0),(s,i’,INF,0)。对四联通的格子,若Hi>Hj,则加边(i’,j,INF,0),若格子i在边界上则加边(i’,t,INF,0)。限制增广次数小于等于K。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstdlib>
#include<map>
#include<vector>
#include<ctime>
#include<stack>
#define mp make_pair
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define pb push_back
#define ll long long
#define ull unsigned long long

using namespace std;

inline ll read()
{
    long long f=1,sum=0;
    char c=getchar();
    while (c<'0' || c>'9')
    {
        if (c=='-') f=-1;
        c=getchar();
    }
    while (c>='0' && c<='9')
    {
        sum=sum*10+c-'0';
        c=getchar();
    }
    return sum*f;
}
const int MAXN=10010;
const int MAXM=100010;
struct edge
{
    int next,to,val,cost,from;
};
edge e[MAXM];
int head[MAXN],cnt=1;
void addedge(int u,int v,int w,int cost)
{
    e[++cnt].next=head[u];
    e[cnt].to=v;
    e[cnt].val=w;
    e[cnt].from=u;
    e[cnt].cost=cost;
    head[u]=cnt;
}
queue <int> q;
int dis[MAXN],from[MAXN],s,t;
bool inqueue[MAXN];
bool spfa()
{
    memset(dis,inf,sizeof(dis));
    dis[s]=0;
    q.push(s);
    inqueue[s]=1;
    while (!q.empty())
    {
        int x=q.front();
        q.pop();
        for (int i=head[x];i;i=e[i].next)
        {
            int v=e[i].to;
            if (e[i].val && dis[v]>dis[x]+e[i].cost)
            {
                dis[v]=dis[x]+e[i].cost;
                from[v]=i;
                if (!inqueue[v])
                    inqueue[v]=1,q.push(v);
            }
        }
        inqueue[x]=0;
    }
    if (dis[t]<INF) return 1;
    return 0; 
}
int min_cost_flow()
{
    int flow=INF;
    for (int i=t;i!=s;i=e[from[i]].from)
        flow=min(flow,e[from[i]].val);
    for (int i=t;i!=s;i=e[from[i]].from)
    {
        e[from[i]].val-=flow;
        e[from[i]^1].val+=flow;
        int v=e[from[i]].to;
        if (v>9) v-=9;
    } 
    return -dis[t];
}
const int dx[4]={-1,0,1,0};
const int dy[4]={0,-1,0,1};
int n,a[55][55],h[55][55];
int pos(int x,int y)
{
    return (x-1)*n+y;
}
void build()
{
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++)
        {
            int p=pos(i,j),p1=p+n*n;
            addedge(p,p1,1,-a[i][j]),addedge(p1,p,0,a[i][j]);
            addedge(p,p1,INF,0),addedge(p,p1,0,0);
            addedge(s,p,INF,0),addedge(p,s,0,0);
            for (int k=0;k<4;k++)
            {
                int nx=i+dx[k],ny=j+dy[k];
                if (nx<=0 || ny<=0 || nx>n || ny>n) continue;
                int p2=pos(nx,ny);
                if (h[i][j]>h[nx][ny])
                    addedge(p1,p2,INF,0),addedge(p2,p1,0,0);
            } 
            if (i==1 || j==1 || i==n || j==n)
                addedge(p1,t,INF,0),addedge(t,p1,0,0);
        }
}
int main()
{
    int T;
    scanf("%d",&T);
    while (T--)
    {
        memset(head,0,sizeof(head));
        cnt=1;
        int k;
        scanf("%d%d",&n,&k);
        s=0,t=2*n*n+1;
        for (int i=1;i<=n;i++)
            for (int j=1;j<=n;j++)
                scanf("%d",&a[i][j]);
        for (int i=1;i<=n;i++)
            for (int j=1;j<=n;j++)
                scanf("%d",&h[i][j]);
        build();
        int num=0,ans=0;
        while (spfa())
        {
            num++;
            ans+=min_cost_flow();
            if (num==k) break;
        }
        printf("%d\n",ans);
    }
    return 0;
}

HOJ 2739 The Chinese Postman Problem

链接又来了

题目大意:带权有向图上的中国邮路问题:一名邮递员需要经过每条有向边至少一次,最后回到出发点,一条边多次经过权值要累加,问最小总权值是多少。

思路:先对原图判断是否联通,或有无一个点的出入度为0就无解。设一个点入度和出度的差x。若x>0,加边(s,i,x,0),否则加边(i,t,-x,0).对于原图中的每条边,加边(i,j,INF,val[i][j]).求一次费用流,费用加上原图所有边权即可。

代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstdlib>
#include<map>
#include<vector>
#include<ctime>
#include<stack>
#define mp make_pair
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define pb push_back
#define ll long long
#define ull unsigned long long

using namespace std;

inline ll read()
{
    long long f=1,sum=0;
    char c=getchar();
    while (c<'0' || c>'9')
    {
        if (c=='-') f=-1;
        c=getchar();
    }
    while (c>='0' && c<='9')
    {
        sum=sum*10+c-'0';
        c=getchar();
    }
    return sum*f;
}
const int MAXN=10010;
const int MAXM=100010;
struct edge
{
    int next,to,val,cost,from;
};
edge e[MAXM];
int head[MAXN],cnt=1;
void addedge(int u,int v,int w,int cost)
{
    e[++cnt].next=head[u];
    e[cnt].to=v;
    e[cnt].val=w;
    e[cnt].from=u;
    e[cnt].cost=cost;
    head[u]=cnt;
}
queue <int> q;
int dis[MAXN],from[MAXN],s,t;
bool inqueue[MAXN];
bool spfa()
{
    memset(dis,inf,sizeof(dis));
    dis[s]=0;
    q.push(s);
    inqueue[s]=1;
    while (!q.empty())
    {
        int x=q.front();
        q.pop();
        for (int i=head[x];i;i=e[i].next)
        {
            int v=e[i].to;
            if (e[i].val && dis[v]>dis[x]+e[i].cost)
            {
                dis[v]=dis[x]+e[i].cost;
                from[v]=i;
                if (!inqueue[v])
                    q.push(v),inqueue[v]=1;
            }
        }
        inqueue[x]=0;
    }
    if (dis[t]<INF) return 1;
    return 0;
}
int min_cost_flow()
{
    int flow=INF;
    for (int i=t;i!=s;i=e[from[i]].from)
        flow=min(flow,e[from[i]].val);
    for (int i=t;i!=s;i=e[from[i]].from)
    {
        e[from[i]].val-=flow;
        e[from[i]^1].val+=flow;
    }
    return dis[t];
}
int in[110],out[110],a[110][110],u[2010],v[2010],w[2010];
bool visit[MAXN];
int n;
void dfs(int x)
{
    visit[x]=1;
    for (int i=1;i<=n;i++)
    {
        if (visit[i]) continue;
        if (a[x][i]) dfs(i);
    }
}
int main()
{
    int T;
    scanf("%d",&T);
    while (T--)
    {
        memset(head,0,sizeof(head));
        cnt=1;
        memset(a,0,sizeof(a));
        memset(visit,0,sizeof(visit));
        memset(in,0,sizeof(in));
        memset(out,0,sizeof(out));
        int m,ans=0;
        scanf("%d%d",&n,&m);
        for (int i=1;i<=m;i++)
        {
            scanf("%d%d%d",&u[i],&v[i],&w[i]);
            u[i]++,v[i]++;
            in[v[i]]++,out[u[i]]++;
            ans+=w[i];
            a[u[i]][v[i]]=1;
        }
        dfs(1);
        bool flag=false;
        for (int i=1;i<=n;i++)
            if (!in[i] || !out[i] || !visit[i])
            {
                cout<<-1<<endl;
                flag=true;
                break;
            }
        if (flag) continue;
        s=0,t=n+1;
        for (int i=1;i<=n;i++)
        {
            int x=in[i]-out[i];
            if (x>0)
                addedge(s,i,x,0),addedge(i,s,0,0);
            else if (x<0)
                addedge(i,t,-x,0),addedge(t,i,0,0);
        }
        for (int i=1;i<=m;i++)
            addedge(u[i],v[i],INF,w[i]),addedge(v[i],u[i],0,-w[i]);
        while (spfa())
            ans+=min_cost_flow();
        printf("%d\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值