【BZOJ-2668】交换棋子 最小费用最大流

2668: [cqoi2012]交换棋子

Time Limit: 3 Sec  Memory Limit: 128 MB
Submit: 1055  Solved: 388
[Submit][Status][Discuss]

Description

有一个nm列的黑白棋盘,你每次可以交换两个相邻格子(相邻是指有公共边或公共顶点)中的棋子,最终达到目标状态。要求第i行第j列的格子只能参与mi,j次交换。

Input

第一行包含两个整数nm(1<=nm<=20)。以下n行为初始状态,每行为一个包含m个字符的01串,其中0表示黑色棋子,1表示白色棋子。以下n行为目标状态,格式同初始状态。以下n行每行为一个包含m个0~9数字的字符串,表示每个格子参与交换的次数上限。

Output

输出仅一行,为最小交换总次数。如果无解,输出-1。

Sample Input

3 3
110
000
001
000
110
100
222
222
222

Sample Output

4

HINT

Source

Solution

这是一道建图比较有趣的费用流.

想到用费用流比较容易,很显然,连法必然是S连向原图中的黑点,新图中的黑点连向T,问题在于中间的连边,如何利用容量限制两个点。

自己一开始立马想到拆点,但是是很naive的一拆二,限制容量,但这样并不可以。

实际上这个题需要一个点拆成3个点,我们把点$x$拆成$x_{1},x_{2},x_{3}$,并连出$x_{1}-->x_{2}-->x_{3}$

其中$x_{1}-->x_{2}$表示$x$这个点最多流入的流量,$x_{2}-->x_{3}$表示$x$这个点最多流出的流量。

对于一个点$x$,如果只是原图的黑点,那么限制$<x_{1},x_{2}>:cap=\frac{use[x]}{2};cost=0 ; <x_{2},x_{3}>:cap=\frac{use[x]+1}{2};cost=0$

并且连$S-->x_{2} : cap=1;cost=0$

对于一个点$x$,如果只是新图的黑点,那么限制$<x_{1},x_{2}>:cap=\frac{use[x]+1}{2};cost=0 ; <x_{2},x_{3}>:cap=\frac{use[x]}{2};cost=0$

并且连$x_{2}-->T : cap=1;cost=0$

如果一个点$x$,如果在原图和新图中都是白点或者都是黑点,那么我们限制$<x_{1},x_{2}>:cap=\frac{use[x]}{2};cost=0 ; <x_{2},x_{3}>:cap=\frac{use[x]}{2};cost=0$

对于图中的八连通格两两$x ; y$,连边$x_{3}-->y_{1} :cap=INF ; cost=1$流经此边,表示交换一次。

上述的限制方法,实际上是对网格的每个位置的流量变化进行讨论得到的,因为是一个最值情况,所以很多流入流出限制都无法完全流满。

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
#define MAXM 100010
#define MAXN 2000
int N,M,used[50][50];
char st[50][50],ed[50][50],us[50][50];
struct EdgeNode{int next,to,cap,cost;}edge[MAXM];
int cnt=1,head[MAXN];
inline void AddEdge(int u,int v,int w,int c) {cnt++; edge[cnt].next=head[u]; head[u]=cnt; edge[cnt].to=v; edge[cnt].cap=w; edge[cnt].cost=c;}
inline void InsertEdge(int u,int v,int w,int c) {AddEdge(u,v,w,c); AddEdge(v,u,0,-c);}
int dis[MAXN],S,T,Cost,Flow;
bool mark[MAXN];
#define INF 0x7fffffff
inline bool SPFA()
{
    memset(mark,0,sizeof(mark));
    for (int i=S; i<=T; i++) dis[i]=INF;
    queue<int>q;
    q.push(S); dis[S]=0; mark[S]=1;
    while (!q.empty())
        {
            int now=q.front(); q.pop(); mark[now]=0;
            for (int i=head[now]; i; i=edge[i].next)
                if (edge[i].cap && dis[edge[i].to]>dis[now]+edge[i].cost)
                    {
                        dis[edge[i].to]=dis[now]+edge[i].cost;
                        if (!mark[edge[i].to]) q.push(edge[i].to),mark[edge[i].to]=1;
                    }
        }
    return dis[T]!=INF;
}
inline int DFS(int loc,int low)
{
    mark[loc]=1;
    if (loc==T) return low;
    int used=0,w;
    for (int i=head[loc]; i; i=edge[i].next)
        if (edge[i].cap && !mark[edge[i].to] && dis[edge[i].to]==dis[loc]+edge[i].cost)
            {
                w=DFS(edge[i].to,min(edge[i].cap,low-used));
                edge[i].cap-=w; edge[i^1].cap+=w; used+=w; Cost+=w*edge[i].cost;
                if (low==used) return used;
            }
    return used;
}
inline int zkw()
{
    int re=0;
    while (SPFA())
        {
            mark[T]=1;
            while (mark[T]) memset(mark,0,sizeof(mark)),re+=DFS(S,INF);
        }
    return re;
}
int id[50][50][4],ID,black,white;
int dx[10]={-1,-1,-1,0,0,1,1,1},dy[10]={-1,0,1,-1,1,-1,0,1};
inline bool check(int x,int y) {return x>=1 && x<=N && y>=1 && y<=M;}
void BuildGraph()
{
    for (int i=1; i<=N; i++)
        for (int j=1; j<=M; j++)
            for (int k=1; k<=3; k++)
                id[i][j][k]=++ID;
    S=0,T=ID+1;
    for (int i=1; i<=N; i++)
        for (int j=1; j<=M; j++)
            {
                if (st[i][j]==‘0‘ && ed[i][j]==‘1‘)
                    black++,
                    InsertEdge(S,id[i][j][2],1,0),
                    InsertEdge(id[i][j][1],id[i][j][2],used[i][j]/2,0),
                    InsertEdge(id[i][j][2],id[i][j][3],(used[i][j]+1)/2,0);
                if (st[i][j]==‘1‘ && ed[i][j]==‘0‘)
                    white++,
                    InsertEdge(id[i][j][2],T,1,0),
                    InsertEdge(id[i][j][1],id[i][j][2],(used[i][j]+1)/2,0),
                    InsertEdge(id[i][j][2],id[i][j][3],used[i][j]/2,0);
                if (st[i][j]==ed[i][j])
                    InsertEdge(id[i][j][1],id[i][j][2],used[i][j]/2,0),
                    InsertEdge(id[i][j][2],id[i][j][3],used[i][j]/2,0);
            }
    for (int i=1; i<=N; i++)
        for (int j=1; j<=M; j++)
            for (int x,y,k=0; k<8; k++)
                {
                    x=i+dx[k],y=j+dy[k];
                    if (check(x,y)) InsertEdge(id[i][j][3],id[x][y][1],INF,1);
                }
}
int main()
{
    scanf("%d%d",&N,&M);
    for (int i=1; i<=N; i++) scanf("%s",st[i]+1);
    for (int i=1; i<=N; i++) scanf("%s",ed[i]+1);
    for (int i=1; i<=N; i++) scanf("%s",us[i]+1);
    for (int i=1; i<=N; i++)
        for (int j=1; j<=M; j++) used[i][j]=us[i][j]-‘0‘;
    BuildGraph();
    Flow=zkw();
    printf("%d\n",black==white? (Flow==black? Cost : -1) : -1);
    return 0;
} 

脑残RE了好几次...

时间: 2024-08-06 15:30:24

【BZOJ-2668】交换棋子 最小费用最大流的相关文章

BZOJ 2668 交换棋子(费用流)

题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=2668 题意:有一个n行m列的黑白棋盘,你每次可以交换两个相邻格子中的棋子,最终达到目标状态.要求第i行第j列的格子只能参与m[i,j]次交换. 思路: 我们将1看做要移动的数字,将0看做空白.那么若1在始末状态个数不同则无解:如某个格子始末状态均有1则这个格子的1对结果无影响,可以将其都置为0.将每个格子拆为为个点p0,p1,p2: (1)若格子初始为1,则连边:<s,p0,1,0>

BZOJ 2668 [cqoi2012]交换棋子 | 最小费用最大流

传送门 BZOJ 2668 题解 同时分别限制流入和流出次数,所以把一个点拆成三个:入点in(x).中间点mi(x).出点ou(x). 如果一个格子x在初始状态是黑点,则连(S, mi(x), 1, 0) 如果x在目标状态是黑点,则连(mi(x), T, 1, 0) 设x的交换次数限制是w 如果x在两种状态中颜色相同,则连(in(x), mi(x), w / 2, 0), (mi(x), ou(x), w / 2, 0) 如果x只在初始状态为黑色,则连(in(x), mi(x), w / 2,

BZOJ 1927 星际竞速(最小费用最大流)

题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=1927 题意:一个图,n个点.对于给出的每条边 u,v,w,表示u和v中编号小的那个到编号大的那个的时间为w.另外有n个值Ai,表示从任何一个点到达i点的时间为Ai.初始时你在n个点之外的一个 点上,我们称其为初始点B.要求从B出发,遍历n个点每个点一次,求最小时间.显然开始你只能使用Ai从B到达n个点中的某个点,因为B到n个点中没有其 他的边. 思路:因为最后停在了某个点上,那么从B出

BZOJ 2424: [HAOI2010]订货(最小费用最大流)

最小费用最大流..乱搞即可 ------------------------------------------------------------------------------ #include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<queue> #define rep( i, n ) for( int i = 0; i <

BZOJ 1070: [SCOI2007]修车(最小费用最大流)

建图很神奇..建完图其实就是裸的费用流了.. -------------------------------------------------------------- #include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #include<vector> #include<queue> #define rep(i,n) for(int i

bzoj 1927 星际竞速 —— 最小费用最大流

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=1927 首先注意到这是个DAG: 考虑每个点从哪里来,可以是瞬移来的,也可以是从某个点走过来的,而从每个点走出去只能用一次: 所以拆点,i 表示从这个点走出去,n+i 表示来到这个点: 建图: 1.瞬移:S 向 n+i 连边权 a[i],流量1的边 2.走过来:如果 i 能走到 j,那么 i 向 n+j 连边权 w,流量1的边 然后 S 向 i 连边权0,流量1的边,表示一个点只能走出去一次

BZOJ 2324 营救皮卡丘(最小费用最大流)

题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=2324 题意:n+1个城市(0到n).初始时K个 人都在0城市.城市之间有距离.要求(1)遍历完n个城市(有一个人遍历了某个城市就算这个城市被遍历了):(2)遍历i城市前必须遍历完前i-1个城 市,并且在遍历前i-1个城市时不能经过大于等于i的城市.在满足(1)(2)的前提下使得K个人走的总距离最小. 思路:我们先看在实际情况下可以怎么走. (1)某个人遍历完某个城市后停在那里,以后不再

BZOJ 1449 球队收益(最小费用最大流)

题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=1449 题意: 思路:首先,我们假设后面的M场比赛两方都是输的,即初始时的lose[i]再加上i参加的场次.这样,后面对于i,每赢一场的收益增加值为: 之后win[i]++,lose[i]--.至此,我们得到建图的方法: (1)源点到每场比赛连流量1,费用0: (2)每场比赛向双方连流量1,费用0: (3)每个人到汇点连x条边(x为该人在M场比赛中出现的次数),流量1,费用为上面计算出的

BZOJ 1061 志愿者招募(最小费用最大流)

题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=1061 题意:申奥成功后,布布经过不懈努力,终于 成为奥组委下属公司人力资源部门的主管.布布刚上任就遇到了一个难题:为即将启动的奥运新项目招募一批短期志愿者.经过估算,这个项目需要N 天才能完成,其中第i 天至少需要Ai 个人. 布布通过了解得知,一共有M 类志愿者可以招募.其中第i 类可以从第Si 天工作到第Ti 天,招募费用是每人Ci 元.新官上任三把火,为了出色地完成自己的工作,布