【费用流模型】BZOJ2668 UVA1317 UVA1486 UVA1104

1.BZOJ2668
按照棋盘建图。
为了满足格子 (i,j) 只能参与 m(i,j) 次交换,将每一个格子拆成两个,连边。若该格子的起始状态和结束状态不同,那么容量为 ceil[m(i,j)/2] ;否则容量为 floor[m(i,j)/2]
相邻的格子从 i 连向 j ,容量为+,费用为 1
跑一次最小费用最大流即可。若最大流+不需要动的白棋 = 所有的白棋数,则答案为最小费用;否则无解。

问题是对i i 的容量如何设定?
对于起始状态和结束状态相同的格子,白棋交换进去必定会交换出来。同理,对于起始状态和结束状态不同的格子,必定有一个白棋交换进去不需要交换出来。

附上建模的代码。

const int dx[]={-1,-1,-1,0,0,1,1,1};
const int dy[]={-1,0,1,-1,1,-1,0,1};
void build()
{
    memset(adj,-1,sizeof adj), pos=0;
    s=n*m*2;
    t=s+1;
    int x, y;
    for(int i=0;i<n;++i)
    {
        for(int j=0;j<m;++j)
        {
            if(b[i][j]=='1'&&e[i][j]=='0')
            {
                add(s,i*m+j,0,1);
                add(i*m+j,i*m+j+n*m,0,(num[i][j]-'0'+1)/2);
                ++cnt;
            }
            else if(b[i][j]=='0'&&e[i][j]=='1')
            {
                add(i*m+j+n*m,t,0,1);
                add(i*m+j,i*m+j+n*m,0,(num[i][j]-'0'+1)/2);
            }
            else
                add(i*m+j,i*m+j+n*m,0,(num[i][j]-'0')/2);
            for(int k=0;k<8;++k)
            {
                x=i+dx[k], y=j+dy[k];
                if(x>=0&&y>=0&&x<n&&y<m)
                    add(i*m+j+n*m,x*m+y,1,inf);
            }
        }
    }
}

2.UVA1317
题目大意:有 n 个订单,每一个订单的时间为[i,j](0<i<=j<=365),在同一时间只能同时满足 2 个订单。求最大利润。

按照365天分别建立节点。第 i 个节点向第i+1个节点连边,容量为 + ,费用为 0 。对于每一个订单,从i j+1 连边,容量为 1 ,费用为利润。源点0向节点 1 连边,容量为2,费用为 0 366向汇点连边,容量为 + ,费用为 0 。跑一次最大费用最大流。

解释一下:
i个节点向第 i+1 个节点连边表示之前剩下的可完成的订单数 (0,1,2)
源点 0 向节点1连边容量为 2 表示同一时间最多能完成的订单数。
对于每一个订单,从i j+1 连边,容量为 1 表示占用了一个工作量。

3.UVA1486
题目大意:有n个节点 m 条边,每条边的容量为ci,费用为 ai ,但费用的计算方式为 aiflow2 。求从源点 1 k到汇点 n 的最小费用。

将每一条边拆成ci条边,第 i 条边的费用为ai(2i1),那么当经过这一系列边的流量为 flow 时,由于是最小费用最大流,那么费用之和为 aiflow2

现在将其扩展一下。
若经过某一条边的费用由一个函数 f(flow) 决定,该边的容量为 c 。那么将这一条边拆成c条,第 i 条的费用为f(i)f(i1)
4.UVA1104
题目大意:有一个 nn 的矩阵,其中’C’表示此处已经放了一个数,’/’表示此处不能放数,’.’表示此处可以放一个数也可以不放。要求第

i 行和第i列放的数一样多,任意一列或者一行放的数不能超过放数总数的 a/b 。求最多能放多少数。

一般像这种矩阵的题,分别以行、列建立节点。
第二个限制,可以限制节点到源点(汇点)的容量;而第一个限制就……

大神题解参上 题解链接

统计所有能放得格子,选择最少的格子不放即可。
对于第一个限制,枚举每行每列最多放 x 个数。(x为枚举变量)
从源点连边到 i ,容量为h[i],表示第 i 行最多能放这么多数;从i连边到汇点,容量为 l[i] ,表示第 i 列最多能放这么多数。
i连边到 i ,容量为 x ,表示第i行、列最多放 x 个数。
对于那些可以不放的格子(i,j),从 i 连边到j,容量为 1 ,费用为1,表示不放这个格子需要花费 1

之后跑一次最小费用最大流。
若最大流为能放格子的总数,说明这个x是合法的。同时要满足第二个限制,就判断一下 x 是否小于(totcost)a/b,其中 tot 表示格子总数, cost 表示没有放数的格子总数。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#define MAXN 105
#define MAXM 10005
#define inf 0x3f3f3f3f
#define min(a,b) ((a)<(b)?(a):(b))
#define max(a,b) ((a)>(b)?(a):(b))
using namespace std;

int n, a, b, h[45], l[45], s, t, out;
int tot, tmp, ans, sum;
char num[45][45];

struct node
{
    int v, cap, w, next;
}edge[MAXM];
int adj[MAXN], pos;
int d[MAXN], temp[MAXN], pre[MAXN];

inline void add(int a,int b,int c,int d)
{
    edge[pos].v=b, edge[pos].w=c, edge[pos].cap=d, edge[pos].next=adj[a];
    adj[a]=pos;
    ++pos;
    edge[pos].v=a, edge[pos].w=-c, edge[pos].cap=0, edge[pos].next=adj[b];
    adj[b]=pos;
    ++pos;
}

bool in[MAXN];
queue<int>q;
bool bfs()
{
    memset(d,inf,sizeof d);
    d[s]=0;
    q.push(s);
    int u, v;
    while(!q.empty())
    {
        u=q.front();q.pop();
        in[u]=0;
        for(int p=adj[u];~p;p=edge[p].next)
            if(edge[p].cap>0)
            {
                v=edge[p].v;
                if(d[v]>d[u]+edge[p].w)
                {
                    d[v]=d[u]+edge[p].w;
                    pre[v]=u, temp[v]=p;
                    if(!in[v])in[v]=1, q.push(v);
                }
            }
    }
    return d[t]!=inf;
}

void isap()
{
    ans=sum=0;
    int mv;
    while(bfs())
    {
        mv=inf;
        for(int i=t;i!=s;i=pre[i])
            mv=min(mv,edge[temp[i]].cap);
        sum+=mv;
        for(int i=t, p;i!=s;i=pre[i])
        {
            p=temp[i];
            edge[p].cap-=mv, edge[p^1].cap+=mv;
            ans+=edge[p].w*mv;
        }
    }
}

int main()
{
    int CNT=0;
    while(~scanf("%d%d%d",&n,&a,&b)&&n+a+b)
    {
        tot=tmp=0;
        memset(h,0,sizeof h), memset(l,0,sizeof l);
        s=n+n;t=s+1;
        for(int i=0;i<n;++i)
        {
            scanf("%s",num[i]);
            for(int j=0;j<n;++j)
                if(num[i][j]!='/')
                {
                    ++tot;
                    ++h[i], ++l[j];
                    if(num[i][j]=='C')
                        ++tmp;
                }
        }

        out=-1;
        for(int cnt=0;cnt<=n;++cnt)
        {
            memset(adj,-1,sizeof adj), pos=0;
            for(int i=0;i<n;++i)
            {
                add(s,i,0,h[i]);
                add(i+n,t,0,l[i]);
                add(i,i+n,0,cnt);
            }
            for(int i=0;i<n;++i)
                for(int j=0;j<n;++j)
                    if(num[i][j]=='.')
                        add(i,j+n,1,1);
            isap();
            if(sum==tot&&cnt*b<=(tot-ans)*a)
                out=max(out,tot-ans);
        }

        printf("Case %d: ", ++CNT);
        if(out==-1)puts("impossible");
        else printf("%d\n",out-tmp);
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值