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

输入格式:

第一行包含两个整数\(n,m(1<=n, m<=20)\)。

以下\(n\)行为初始状态,每行为一个包含\(m\)个字符的\(01\)串,其中\(0\)表示黑色棋子,\(1\)表示白色棋子。

以下\(n\)行为目标状态,格式同初始状态。

以下\(n\)行每行为一个包含\(m\)个\(0-9\)数字的字符串,表示每个格子参与交换的次数上限。

输出格式:

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

输入样例#1:

3 3
110
000
001
000
110
100
222
222
222

输出样例#1:

4

最近一直在学习网络流。写到这个题目的时候,第一反应是:“这怎么可能是网络流呢?”用了一个下午写出来这道题后,感觉其思路实在妙极。

本题解力求让像我一样初学网络流(初学OI)的人能够看懂,如果还存在疑惑的话欢迎联系我哦~

看这个题目,很容易想到:可以记录黑色棋子的起始和终结位置,想办法去让棋子从起始位置走到终止位置,一一匹配。棋子在棋盘上走,走的过程中,棋子受到必须成功匹配(最大流)和在此基础上费用最小的约束条件。这样考虑的话,跑费用流自然是再合适不过了。如下图所示:

进一步考虑会发现,棋子的交换可以被视为在棋盘上的坐标移动。想要连通起始点和终止点,只需要在二者之间建立棋盘的八连通图,让棋子在对应位置上进出棋盘即可。

但是现在,有一个关键的问题:起始点和终止点作交换的时候, 消耗流量是\(1\),但是对于中间节点,消耗流量却都应该是\(2\)。如果单纯的考虑把一个点拆分成一条边的话,无法处理这种边界情况,事情就变得相对比较麻烦。

回归题目来考虑,题目要求是交换,那么交换就有交换进来和交换出去这两种交换方法。根据这个给我们的灵感,我们可以考虑把一个点拆成\(3\)个:\(inn\),\(mid\)和\(out\),把原本的最大访问量均分在两端上,而把每次的进出流量视为\(1\)。这样同时又解决了进出棋盘的问题:直接在\(mid\)处进入棋盘就不用考虑其他麻烦的事情了。

那么流量均分的想法是否正确呢?基本上是对的。但是,现在我们有了\(inn\)->\(mid\)和\(mid\)->\(out\)两种边,如果边权是奇数,进出棋盘需要的流量只为\(1\),原本不应该被忽略的零头\(1\)可能会被忽略或者非最优地分配。

所以这里又牵涉到了这一点边界的处理问题。如果棋盘开始和结束都有或都没有该棋子,那么我们对可用点权\(maxf\)取\(1/2\)。否则的话,分别考虑进入和出去的情况:

可以看到,进入时的\(mid\)->\(out\),出去时的\(inn\)->\(mid\)会有一条耗流为\(1\)的边,我们考虑如果这个点不是既进又出节点,就给其存在\(1\)耗流边的一部分尝试多分配一点"零头"流量(即偶数分配为\(n/2\)或\((n+1)/2\)都一样,而奇数则分配为\(n/2\):\((n+1)/2\)。)

为了便于各位理清思路,这里本人贴一下建图流程:

  • 初始点->\(S\) \(f=1\) \(w=0\)
  • 最终点<-\(T\) \(f=1\) \(w=0\)
  • 初始点->对应坐标的\(mid\)节点 \(f=1\) \(w=0\)
  • 对应坐标的\(mid\)节点->终止点 \(f=1\) \(w=0\)
  • 棋盘内部的八连通:(\(out\)->\(in\)) \(f=INF\) \(w=1\)
  • \(inn\)->\(mid\)和\(mid\)->\(out\):\(w=1\),根据情况确认选择 \(f=maxf/2\) 或者 \(f=(maxf+1)/2\)

至此,问题得以完美解决。代码冗长求轻喷。

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define MAXN 2010
#define MAXM 64010
#define INF 0x3f3f3f3f
#define fpop(x) x.front();x.pop()
using namespace std; int pre_node[MAXN],pre_edge[MAXN]; char ch,mp_bg[25][25],mp_ed[25][25]; int n,m,cnt=-1,dis[MAXN],vis[MAXN],flow[MAXN],head[MAXN],maxf[25][25]; struct edge{
int nxt,to,w,f;
}e[MAXM]; inline int _bg(int x,int y){return n*m*0+(x-1)*m+y;}//起始点[x,y]的编号
inline int _ed(int x,int y){return n*m*1+(x-1)*m+y;}//目标点[x,y]的编号
inline int _inn(int x,int y){return n*m*2+(x-1)*m+y;}//棋盘[x,y]的Inn点编号
inline int _mid(int x,int y){return n*m*3+(x-1)*m+y;}//棋盘[x,y]的mid点标号
inline int _out(int x,int y){return n*m*4+(x-1)*m+y;}//棋盘[x,y]的Out点编号 inline bool in_map(int x,int y){
return 1<=x && x<=n && 1<=y && y<=m;
}//判断是否越界 inline void add_edge(int from,int to,int flw,int val){
e[++cnt].nxt=head[from];
e[cnt].to=to;
e[cnt].f=flw;
e[cnt].w=val;
head[from]=cnt;
} queue<int>que; inline bool spfa(int s,int t){
memset(vis,0,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
memset(flow,0x3f,sizeof(flow));
que.push(s); vis[s]=true; dis[s]=0;
while(!que.empty()){
int u=fpop(que);
for(int i=head[u];~i;i=e[i].nxt){
int v=e[i].to;
if(dis[v]>dis[u]+e[i].w && e[i].f){
dis[v]=dis[u]+e[i].w;
flow[v]=min(flow[u],e[i].f);
pre_node[v]=u;
pre_edge[v]=i;
vis[v]=true;que.push(v);
}
}
}
return dis[t]!=INF;
} int mv[8][2]={{1,0},{-1,0},{0,1},{0,-1},{1,1},{1,-1},{-1,1},{-1,-1}}; int main(){
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
scanf(" %c",&mp_bg[i][j]);
}
}
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
scanf(" %c",&mp_ed[i][j]);
}
}
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
scanf(" %c",&ch);
maxf[i][j]=ch-'0';
//最大经过次数
}
}
//输入起始态和目标态棋盘
int s=0,t=n*m*5+1;
int cnt_1=0,cnt_2=0;
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
if(mp_bg[i][j]==mp_ed[i][j]){
add_edge(_inn(i,j),_mid(i,j),maxf[i][j]/2,0);
add_edge(_mid(i,j),_inn(i,j),000000000000,0); add_edge(_mid(i,j),_out(i,j),maxf[i][j]/2,0);
add_edge(_out(i,j),_mid(i,j),000000000000,0);
}else{
if(mp_bg[i][j]=='1'){
add_edge(_inn(i,j),_mid(i,j),(maxf[i][j]+0)/2,0);
add_edge(_mid(i,j),_inn(i,j),000000000000,0); add_edge(_mid(i,j),_out(i,j),(maxf[i][j]+1)/2,0);
add_edge(_out(i,j),_mid(i,j),000000000000,0);
}
if(mp_ed[i][j]=='1'){
add_edge(_inn(i,j),_mid(i,j),(maxf[i][j]+1)/2,0);
add_edge(_mid(i,j),_inn(i,j),000000000000,0); add_edge(_mid(i,j),_out(i,j),(maxf[i][j]+0)/2,0);
add_edge(_out(i,j),_mid(i,j),000000000000,0);
}
} if(mp_bg[i][j]=='1'){
++cnt_1;
//连接源点到初始点 f=1 w=0;
add_edge(s,_bg(i,j),1,0);
add_edge(_bg(i,j),s,0,0);
//连接起始点到棋盘
add_edge(_bg(i,j),_mid(i,j),1,0);
add_edge(_mid(i,j),_bg(i,j),0,0);
}
if(mp_ed[i][j]=='1'){
++cnt_2;
//连接终结点到汇点 f=1 w=0;
add_edge(_ed(i,j),t,1,0);
add_edge(t,_ed(i,j),0,0);
//连接棋盘到终结点
add_edge(_mid(i,j),_ed(i,j),1,0);
add_edge(_ed(i,j),_mid(i,j),0,0);
}
//棋盘的八连通边 f=INF w=1;
for(int k=0;k<8;++k){
int ni=i+mv[k][0];
int nj=j+mv[k][1];
if(in_map(ni,nj)){
//从点[i,j]的out连接点[ni,nj]的inn
add_edge(_out(i,j),_inn(ni,nj),INF,+1);
add_edge(_inn(ni,nj),_out(i,j),000,-1);
}
}
}
}
//棋子数变动->No solution
if(cnt_1!=cnt_2){
puts("-1");
return 0;
}
//然后跑费用流
int max_flow=0,min_cost=0;
while(spfa(s,t)){
max_flow+=flow[t];
min_cost+=flow[t]*dis[t];
int u=t;
while(u!=s){
e[pre_edge[u]^0].f-=flow[t];
e[pre_edge[u]^1].f+=flow[t];
u=pre_node[u];
}
}
if(max_flow!=cnt_1){
puts("-1");
return 0;
}
printf("%d\n",min_cost);
}

洛谷 P3159(BZOJ 2668)[CQOI2012]交换棋子的更多相关文章

  1. BZOJ 2668: [cqoi2012]交换棋子

    2668: [cqoi2012]交换棋子 Time Limit: 3 Sec  Memory Limit: 128 MBSubmit: 1112  Solved: 409[Submit][Status ...

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

    传送门 BZOJ 2668 题解 同时分别限制流入和流出次数,所以把一个点拆成三个:入点in(x).中间点mi(x).出点ou(x). 如果一个格子x在初始状态是黑点,则连(S, mi(x), 1, ...

  3. BZOJ.2668.[CQOI2012]交换棋子(费用流zkw)

    题目链接 首先黑白棋子的交换等价于黑棋子在白格子图上移动,都到达指定位置. 在这假设我们知道这题用网络流做. 那么黑棋到指定位置就是一条路径,考虑怎么用流模拟出这条路径. 我们发现除了路径的起点和终点 ...

  4. 2668: [cqoi2012]交换棋子

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

  5. BZOJ2668: [cqoi2012]交换棋子

    题解: 可以戳这里:http://www.cnblogs.com/zig-zag/archive/2013/04/21/3033485.html 其实自己yy一下就知道这样建图的正确性了. 感觉太神奇 ...

  6. [cqoi2012]交换棋子

      2668: [cqoi2012]交换棋子 Time Limit: 3 Sec  Memory Limit: 128 MBSubmit: 1334  Solved: 518[Submit][Stat ...

  7. 【BZOJ2668】[cqoi2012]交换棋子 费用流

    [BZOJ2668][cqoi2012]交换棋子 Description 有一个n行m列的黑白棋盘,你每次可以交换两个相邻格子(相邻是指有公共边或公共顶点)中的棋子,最终达到目标状态.要求第i行第j列 ...

  8. 洛谷 P3307: bzoj 3202: [SDOI2013] 项链

    题目传送门:洛谷P3307.这题在bzoj上是权限题. 题意简述: 这题分为两个部分: ① 有一些珠子,每个珠子可以看成一个无序三元组.三元组要满足三个数都在$1$到$m$之间,并且三个数互质,两个珠 ...

  9. 洛谷 4106 / bzoj 3614 [HEOI2014]逻辑翻译——思路+类似FWT

    题目:https://www.luogu.org/problemnew/show/P4106 https://www.lydsy.com/JudgeOnline/problem.php?id=3614 ...

  10. 洛谷 P3332 BZOJ 3110 [ZJOI2013]K大数查询

    题目链接 洛谷 bzoj 题解 整体二分 Code #include<bits/stdc++.h> #define LL long long #define RG register usi ...

随机推荐

  1. 705 B. Spider Man

    传送门 [http://codeforces.com/contest/705/problem/B] 题意 这题意看原文的真tm难懂Woc,但结合样例就知道大概意思了 两个轮流分环,可以这么理解两个人轮 ...

  2. 12.16 Daily Scrum

      Today's Task Tomorrow's Task 丁辛 实现和菜谱相关的餐厅列表. 实现和菜谱相关的餐厅列表.             邓亚梅             美化搜索框UI. 美 ...

  3. 补充照片:某基同学使用Bing词典

    某基同学使用Bing词典的照片

  4. 《Linux内核分析》第七周笔记 可执行程序的装载

    20135132陈雨鑫 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000  ...

  5. 《Linux内核设计与实现》第3章读书整理

    第三章.进程管理 3.1进程 1.进程就是处于执行期的程序,但进程并不仅仅局限于一段可执行程序代码 2.执行线程: 简称线程,是在进程中活动的对象.每个线程都拥有一个独立的程序计数器.进程栈和一组进程 ...

  6. HelloWorld.php

    没有写博的习惯,从今天开始.近期学习了下php,分享下我的第一个PHP. 工具:Hbuider+Wampserver 利用Wampserver就可以完成PHP脚本的编写和运行,本人之所以选择安装HBu ...

  7. “数学口袋精灵”App的第三个Sprint计划----开发日记(第十一天12.17)

    项目进度: 基本完成一个小游戏,游戏具有:随机产生算式,判断对错功能.通过轻快的背景音乐,音效,给玩家提供一个良好的氛围.  任务分配: 冯美欣:设计"数学口袋精灵"App图标.整 ...

  8. hg命令

    hg常用命令 hg命令跟git命令大同小异 hg version 查看hg版本 hg clone url 克隆代码仓库 hg branch newBranch 创建分支 hg update other ...

  9. Prism6下的MEF:基于微软企业库的Cache

    通常,应用程序可以将那些频繁访问的数据,以及那些需要大量处理时间来创建的数据存储在内存中,从而提高性能.基于微软的企业库,我们的快速创建一个缓存的实现. 新建PrismSample.Infrastru ...

  10. Fantacy团队周二站立会议

    词频分析模型 1.这次站会是周二开的,但是由于我个人的疏忽,哎,不说了. 2.会议时间:2016年3月29日12:03~12:30. 持续时长:27分钟 会议参加成员:组长:杨若鹏 http://ww ...