Description###

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

Input###

第一行包含两个整数n,m(1<=n, m<=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


想法##

首先把题目说的不清楚的地方澄清一下:“第i行第j列的格子只能参与mi,j次交换”所说第i行第j列的棋子指的是每次交换后位于第i行第j列这个位置的棋子,可以是多个,而不是指最原始状态中第i行第j列那个特定的棋子。

很容易发现,我们可以只考虑白色棋子,只要它们都移动到目标状态,剩下的黑棋子也都到目标状态了。

不停地交换听起来好像比较棘手,但其实白棋子只有和身边的黑棋子交换位置才有用。

所以我们就是要给每个白棋子规划一条线路,让它们从原始位置变到目标位置。

很容易想到按原图建图,拆点~

但有个问题,若某个白格子经过某个格子,那么这个格子会被该白格子交换两次;而白格子原位置与目标位置只会被该白格子交换一次。

于是有一个更高级的拆点:一个点拆成三个!(id1,id2,id3)

id1到id2限制其他点与这个点交换的次数,id2到id3限制这个点与其他点交换的次数(即一个是进入的流量,一个是出去的流量)

建图,分类讨论。

  • 对于原状态和目标状态均为黑的格子。

    id1到id3连容量为cap/2,费用为1的边
  • 对于原状态为白,目标状态为黑的格子。

    S到id2连容量为1,费用为0的边

    id1到id2连容量为cap/2,费用为1的边

    id2到id3连容量为(cap+1)/2,费用为1的边
  • 对于原状态为黑,目标状态为白的格子。

    id2到T连容量为1,费用为0的边

    id1到id2连容量为(cap+1)/2,费用为1的边

    id2到id3连容量为cap/2,费用为1的边
  • 对于原状态和目标状态均为白的格子。

    S到id2连容量为1,费用为0的边

    id2到T连容量为1,费用为0的边

    id1到id2连容量为cap/2,费用为1的边

    id2到id3连容量为cap/2,费用为1的边

之后向八连通的格子连边。

注意是某格子的id1连向八连通格子的id3,然后八连通的id1连向这个格子的id3

。。。还是有点复杂的。

接下来就跑个最小费用最大流,看流量是否为总白格子数。

若不是,则输出-1,否则答案为 最小费用/2


代码##

注意细节:

1.某些边的容量为(cap+1)/2,容易想错写成cap/2

2.数组要开够!!

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue> #define INF 1000000000 using namespace std; const int N = 405;
const int M = 1205; struct node{
int v,f,c;
node *next,*rev;
}pool[N*40],*h[M],*pree[M]; /**/
int cnt;
void addedge(int u,int v,int f,int c){
node *p=&pool[++cnt],*q=&pool[++cnt];
p->v=v;p->next=h[u];h[u]=p; p->f=f;p->c=c;p->rev=q;
q->v=u;q->next=h[v];h[v]=q; q->f=0;q->c=-c;q->rev=p;
} int S,T;
int d[M],vis[M],pre[M];
queue<int> que;
bool spfa(){
int u,v;
while(!que.empty()) que.pop();
for(int i=S;i<=T;i++) d[i]=INF;
d[S]=0; vis[S]=1; que.push(S);
while(!que.empty()){
u=que.front(); que.pop();
vis[u]=0;
for(node *p=h[u];p;p=p->next)
if(p->f && d[v=p->v]>d[u]+p->c){
d[v]=d[u]+p->c;
pre[v]=u; pree[v]=p;
if(!vis[v]){
vis[v]=1;
que.push(v);
}
}
}
return d[T]!=INF;
}
void MCMF(int &f,int &c){
f=0; c=0;
int u,w;
while(spfa()){
u=T; w=INF;
while(u!=S){
w=min(w,pree[u]->f);
u=pre[u];
}
f+=w; c+=d[T]*w;
u=T;
while(u!=S){
pree[u]->f-=w;
pree[u]->rev->f+=w;
u=pre[u];
}
}
} int n,m;
char a[23][23],b[23][23],ch[23][23];
int dre[8][2]={-1,-1,-1,0,-1,1,0,-1,0,1,1,-1,1,0,1,1}; int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++) scanf("%s",a[i]);
for(int i=0;i<n;i++) scanf("%s",b[i]);
for(int i=0;i<n;i++) scanf("%s",ch[i]); int w=n*m,id1,id2,id3,tot=0;
S=0; T=w*3+1;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++){
id1=i*m+j+1; id2=id1+w; id3=id2+w;
if(a[i][j]=='0' && b[i][j]=='0')
addedge(id1,id3,(ch[i][j]-'0')/2,2);
else if(a[i][j]=='1' && b[i][j]=='0'){
tot++;
addedge(S,id2,1,0);
addedge(id1,id2,(ch[i][j]-'0')/2,1);
addedge(id2,id3,(ch[i][j]-'0'+1)/2,1); /**/
}
else if(a[i][j]=='0' && b[i][j]=='1'){
addedge(id2,T,1,0);
addedge(id1,id2,(ch[i][j]-'0'+1)/2,1); /**/
addedge(id2,id3,(ch[i][j]-'0')/2,1);
}
else{
tot++;
addedge(S,id2,1,0); addedge(id2,T,1,0);
addedge(id1,id2,(ch[i][j]-'0')/2,1);
addedge(id2,id3,(ch[i][j]-'0')/2,1);
}
for(int k=0;k<8;k++){
int x=i+dre[k][0],y=j+dre[k][1];
if(x>=0 && x<n && y>=0 && y<m){
addedge(id3,x*m+y+1,INF,0);
addedge(x*m+y+1+2*w,id1,INF,0);
}
}
}
int f,c;
MCMF(f,c);
if(f<tot) printf("-1\n");
else printf("%d",c/2); return 0;
}

[bzoj2668] [洛谷P3159] [cqoi2012] 交换棋子的更多相关文章

  1. 洛谷P3159 [CQOI2012]交换棋子

    巧妙的拆点方式,首先把1看成黑点,0看成空的,几次交换就可以看成一条路径 1)从容量上看,这条路径为1-2-2-2-2-2----2-1 2)从费用上看,这条路径每条边费用都是1 于是用一种巧妙的拆点 ...

  2. P3159 [CQOI2012]交换棋子

    思路 相当神奇的费用流拆点模型 最开始我想到把交换黑色棋子看成一个流流动的过程,流从一个节点流向另一个节点就是交换两个节点,然后把一个位置拆成两个点限制流量,然后就有了这样的建图方法 S向所有初始是黑 ...

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

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

  4. BZOJ2668: [cqoi2012]交换棋子

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

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

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

  6. [cqoi2012]交换棋子

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

  7. [洛谷P3158] [CQOI2011]放棋子

    洛谷题目链接:[CQOI2011]放棋子 题目描述 在一个m行n列的棋盘里放一些彩色的棋子,使得每个格子最多放一个棋子,且不同 颜色的棋子不能在同一行或者同一列.有多少祌方法?例如,n=m=3,有两个 ...

  8. 洛谷 P3182 [HAOI2016]放棋子(高精度,错排问题)

    传送门 解题思路 不会错排问题的请移步——错排问题 && 洛谷 P1595 信封问题 这一道题其实就是求对于每一行的每一个棋子都放在没有障碍的地方的方案数. 因为障碍是每行.每列只有一 ...

  9. 洛谷P3158 [CQOI2011]放棋子 组合数学+DP

    题意:在一个m行n列的棋盘里放一些彩色的棋子,使得每个格子最多放一个棋子,且不同颜色的棋子不能在同一行或者同一列.有多少祌方法? 解法:这道题不会做,太菜了qwq.题解是看洛谷大佬的. 设C是组合数, ...

随机推荐

  1. 深度解读 - TDD详细(测试驱动开发)

    本文结构: 什么是 TDD 为什么要 TDD 怎么 TDD FAQ 学习路径 延伸阅读 什么是 TDD TDD 有广义和狭义之分,常说的是狭义的 TDD,也就是 UTDD(Unit Test Driv ...

  2. addEventListener() 方法,事件监听(去哪儿网用到过)

    addEventListener() 方法,事件监听 你可以使用 removeEventListener() 方法来移除事件的监听. 语法 element.addEventListener(event ...

  3. JQ表单选择器和CSS3表单选择器

    JQ表单选择器和CSS3表单选择器 JQ表单选择器 为了使用户能够更加灵活地操作表单,jQuery中加入了表单选择器,利用这个选择器能极其方便的获取到表单的某个或者某类型的元素.表单选择器的介绍如图: ...

  4. opacity兼容性以及存在问题处理

    opacity兼容性以及存在问题处理 opacity兼容性 opacity属性是CSS3的属性,用于设置元素的不透明级别.语法: opacity: value | inherit; ①值value表示 ...

  5. git常用常用操作指令

    GIT操作 1:git init 初始化空的仓库,会在当前文件夹生成一个隐藏.git的文件夹,相当于一个仓库. 2:提交代码的流程:工作代码区-->暂存区 -->主仓库 -->服务器 ...

  6. monaco-editor使用

    monaco-editor是一款非常好用的web代码编辑器,那么如何把他加到自己的项目中呢. 1.下载插件 npm install monaco-editor@0.8.3 2.初始化编辑器值 < ...

  7. VRChat之blender教程

    推荐先看:VRChat模型制作及上传总篇(包含总流程和所需插件):https://www.cnblogs.com/raitorei/p/12015876.html 0.设置中文,安装cat插件 注意  ...

  8. HTML基础常识

    什么是HTML? 超文本标记语言,用来制作网页 浏览器常识: 常见浏览器: 谷歌(Chrome).苹果(Safari) . IE(Edge).欧朋(Opera).火狐(Firefox) 浏览器内核:浏 ...

  9. jquery中如何判断checkbox是否选中

    jquery代码: <script> $(document).ready(function() { $('#checkbox-id').click(function() { if($('# ...

  10. JVM性能监测工具——VisualVM

    Java本身自带了有好几个jvm监测工具,其中jconsole和jvisualvm这两个工具具有图形化界面,可以监测到cpu.类.线程.堆等一些参数,而且具有远程监控的能力. 启动:打开cmd命令窗口 ...