[CQOI2012]交换棋子 网络流
题解:
一开始很快想出了一个接近正解的建图方法,但其实是错误的,不过还是骗了70分_(:зゝ∠)_
首先我们可以观察到棋子有限,但费用多种,其实也就相当于限制了流量,找最小费用
对于初始状态的每一个1,我们连s ---> x flow = 1 cost = 0
对于目标状态的每一个1,我们连x ---> t flow = 1 cost = 0
对于每一个方块,我们向周围八个格子连边 flow = inf , cost = 1(表示交换了一次)
然后就是比较妙难的部分了
首先我们要拆点,因为每个点有流量限制(交换次数)
我们考虑以下三种情况
1,初始状态是0, 目标状态也是0, 我们连x ----> x' flow = limit/2 cost = 0
那么为什么连的是limit/2,而不是limit呢?
我们可以观察到对于这样一个路径1 ----> x----> 3,夹在中间的x被翻转了两次,但流量却只流经了一次,
所以流一次实际是消耗了2的限制,因此我们直接在建边的时候就建limit/2
2,初始状态是1, 目标状态是0, 我们连x ---> x' flow = (limit + 1) / 2 cost = 0;
那么为什么这里又要+1呢?
因为这里本来就有个棋子,但目标状态却没有,因此这个棋子是必须换出去的。
但是这种交换又和上面的不同了,因为这种情况下,点x在路径中的位置是一个端点,因此是这样的x ----> 2,
于是我们观察到这条路径上,x并没有被翻转两次,流量却和上面一样流经了一次,也就是说如果不做任何修改,
这样虽然只翻转了一次,却还是在被当做翻转了2次对待。这显然是不合理的。
就比如这样的情况:
x ----> 2 其中x的limit是1,那么这个时候x上的那个棋子显然是可以转一次就转出去的,但是如果直接按偶数建就把这次机会给忽略掉了。
3,初始状态是0, 目标状态是1, 我们连x ---> x' flow = (limit - 1) / 2 cost = 0;
为什么这里是-1?
因为这里是别的棋子要进来,进来后因为是目标位置,所以就直接去t了,不会流经x ---> x'
但这显然也是要浪费一个机会的,因此我们在开头就减掉这个1.
总的来说就是用1 的流量表示2个翻转次数,如果流一次会导致多统计1,那我们就在开头加一个1补回来多余的消耗
如果流一次会导致统计不到需要统计的那个1,那我们就在开头-1来表示机会被消耗掉了一个
其实应该也算是人类智慧的一种体现吧,,,强行分类嘛。
这里有一个很巧妙的实现:建边的时候直接给limit加上in[i][j] - out[i][j],具体为什么想想就知道啦,不过我觉得这是个巧合emmmm
不过貌似还有另一种解法,拆3个点,中间的边这样连:
x ---> x' flow = limit/2 , cost = 0
x' ---> x'_ flow = limit/2 + (limit % 2 ? 1 : 0)
当然这只是我的口胡,,,具体正确性有待考证
附代码(有非常详细的注释,,,当然也有一些乱七八糟的注释,,,不过其实我觉得直接看代码应该也能懂_(:зゝ∠)_)
#include<bits/stdc++.h>
using namespace std;
#define R register int
#define inf 2139062143
#define AC 6000
#define ac 80000
int n, m, s, all, t, ans, ansflow, cnt, tmp;
int date[ac], Head[AC], Next[ac], haveflow[ac], cost[ac], tot = ;
int dis[AC], disflow[AC], last[AC], in[][], out[][];
int a[] = {-, , , , -, -, , }, b[] = {, , -, , , -, , -};
bool z[AC];
deque<int> q;
char ss[][];
/*因为原来有,后来没有的格子必须要有一次是经过一次翻转然后出去的,因此对于这种,应该要加1的限制,
而原来没有,后来有的格子,必须要有一次是经过一次翻转得到一个棋子的,因此对于这种,应该要-1的限制,
这两种之所以不同就是因为原来有,现在没有的格子是要出去棋子,这种情况下肯定会消耗一个流量,且不会有多消耗的风险,
而原来没有,后来有的格子,因为是从别的格子进来的,因此无法分辨这到底是要停下的流量,还是要出去的流量,
而且完全可能本来是到这里停下的流量跑了出去。这时如果还保留这一个流量,就可能会造成一个点可以翻转两次,
但别的棋子进来那次本来就去掉了一次了,却没有在这上面体现出来,因为流量到了这个点就直接去t了,
根本不会被计入x ---> x'的管道中。
简单来说就是第一种肯定且仅会产生1的流量,而且这个流量将被计入x ---> x'中,因此我们就要多分配1的限制去保证奇数时也可以生效。
而第二种肯定且仅会产生1的流量,但这个流量将不会被计入x ---> x'中,但是实际上这个边应当要统计到它,因为它也消耗了一次翻转限制。
所以就要人为的减去这个限制,以防止偶数时这次流入打破了偶数的条件,却依然按照偶数来跑*/
inline void add(int f, int w, int S, int C)
{
date[++tot] = w, Next[tot] = Head[f], haveflow[tot] = S, cost[tot] = C, Head[f] = tot;
date[++tot] = f, Next[tot] = Head[w], cost[tot] = -C, Head[w] = tot;
// printf("%d ---> %d %d %d\n", f, w, S, C);
} inline int id(int x, int y)
{
return (x - ) * m + y;
}
//error!!!流进来一次,流出去一次,一共消耗了两个流量!
//所以一共块进来最多limit/2次,出去最多limit/2 + 1(单数的话)次(因为不用流进来的消耗)
//因此要拆成3个点。,。。。
void pre()
{
int x;
scanf("%d%d", &n, &m);
all = n * m;
s = all * + , t = s + ;
for(R i = ; i <= n; i++)
{
scanf("%s", ss[i] + );
for(R j = ; j <= m; j++)
{
x = id(i, j);
in[i][j] = ss[i][j] - '';
if(ss[i][j] - '' > ) add(s, x, , ), ++tmp;
for(R k = ; k <= ; k++)
if(i + a[k] > && i + a[k] <= n && j + b[k] > && j + b[k] <= m) //8个格子都要连
add(x + all, id(i + a[k], j + b[k]), inf, );//1 ---> 2 ---> 3这样的路径实际上只有两次翻转,而中间的点将失去两次机会
}//因此只需要在1 ---> 2 2 ---> 3这样的路径中记录cost表示一次翻转就可以了,中间流量限制为limit/2即可(因为失去了两次机会)
}
for(R i = ; i <= n; i++)
{
scanf("%s", ss[i] + );
for(R j = ; j <= m; j++)
{
x = id(i, j);
out[i][j] = ss[i][j] - '';
if(ss[i][j] - '' > )
add(x, t, , ), ++cnt;
//只有原来有,现在没有,也就是要强制移走的时候,才应该给一次机会,多给一个流量让它流走
//因为只有这个时候才会出现一条只有两个点的路径,即整条路径上的点都只消耗一的流量,所有点都是端点
}//因为当需要在此格停下的时候,不用穿过去实现再一次翻转,从而浪费一些流量,因此连向t的应当是原来的点,而不是拆出来的点
}
if(cnt != tmp)
{
printf("-1\n");
exit();
}
for(R i = ; i <= n; i++)
{
scanf("%s", ss[i] + );
for(R j = ; j <= m; j++)
{
x = id(i, j);
tmp = ss[i][j] - '';
tmp += in[i][j] - out[i][j];//通过观察可以发现这样的关系所需要的改动刚好就是in[i][j] - out[i][j]的结果
if(tmp / > ) add(x, x + all, tmp / , );
}
}
} void aru()
{
int x = t;
// printf("%d ", t);
while(x != s)
{
haveflow[last[x]] -= disflow[t];
haveflow[last[x] ^ ] += disflow[t];
x = date[last[x] ^ ];
// printf("<--- %d ",x);
}
// printf(" cost = %d\n", dis[t]);
ans += disflow[t] * dis[t];
ansflow += disflow[t];
} bool spfa()
{
int x, now;
disflow[s] = inf, dis[s] = ;
q.push_front(s), z[s] = true;
while(!q.empty())
{
x = q.front();
q.pop_front();
z[x] = false;
for(R i = Head[x]; i ; i = Next[i])
{
now = date[i];
if(haveflow[i] && dis[now] > dis[x] + cost[i])
{
dis[now] = dis[x] + cost[i];
disflow[now] = min(disflow[x], haveflow[i]);
last[now] = i;
if(!z[now] && now != t)//error!!!now不能等于t
{
z[now] = true;
if(!q.empty() && dis[now] < dis[q.front()]) q.push_front(now);
else q.push_back(now);
}
}
}
}
if(dis[t] != inf) aru();
// printf("!!!%d\n", ans);
return dis[t] != inf;
} void work()
{
memset(dis, , sizeof(dis));
while(spfa()) memset(dis, , sizeof(dis));
if(ansflow >= cnt) printf("%d\n", ans);
else printf("-1\n");
} int main()
{
freopen("in.in","r",stdin);
pre();
work();
fclose(stdin);
return ;
}
[CQOI2012]交换棋子 网络流的更多相关文章
- [cqoi2012]交换棋子
2668: [cqoi2012]交换棋子 Time Limit: 3 Sec Memory Limit: 128 MBSubmit: 1334 Solved: 518[Submit][Stat ...
- BZOJ2668: [cqoi2012]交换棋子
题解: 可以戳这里:http://www.cnblogs.com/zig-zag/archive/2013/04/21/3033485.html 其实自己yy一下就知道这样建图的正确性了. 感觉太神奇 ...
- BZOJ 2668: [cqoi2012]交换棋子
2668: [cqoi2012]交换棋子 Time Limit: 3 Sec Memory Limit: 128 MBSubmit: 1112 Solved: 409[Submit][Status ...
- 【BZOJ2668】[cqoi2012]交换棋子 费用流
[BZOJ2668][cqoi2012]交换棋子 Description 有一个n行m列的黑白棋盘,你每次可以交换两个相邻格子(相邻是指有公共边或公共顶点)中的棋子,最终达到目标状态.要求第i行第j列 ...
- 洛谷 P3159(BZOJ 2668)[CQOI2012]交换棋子
有一个\(n\)行\(m\)列的黑白棋盘,你每次可以交换两个相邻格子(相邻是指有公共边或公共顶点)中的棋子,最终达到目标状态.要求第\(i\)行第\(j\)列的格子只能参与\(m[i][j]\)次交换 ...
- BZOJ.2668.[CQOI2012]交换棋子(费用流zkw)
题目链接 首先黑白棋子的交换等价于黑棋子在白格子图上移动,都到达指定位置. 在这假设我们知道这题用网络流做. 那么黑棋到指定位置就是一条路径,考虑怎么用流模拟出这条路径. 我们发现除了路径的起点和终点 ...
- BZOJ2668:[CQOI2012]交换棋子(费用流)
题目描述 有一个n行m列的黑白棋盘,你每次可以交换两个相邻格子(相邻是指有公共边或公共顶点)中的棋子,最终达到目标状态.要求第i行第j列的格子只能参与mi,j次交换. 输入输出格式 输入格式: 第一行 ...
- [luoguP3159] [CQOI2012]交换棋子(最小费用最大流)
传送门 好难的网络流啊,建图真的超难. 如果不告诉我是网络流的话,我估计就会写dfs了. 使用费用流解决本题,设点 $p[i][j]$ 的参与交换的次数上限为 $v[i][j]$ ,以下为建图方式: ...
- P3159 [CQOI2012]交换棋子
思路 相当神奇的费用流拆点模型 最开始我想到把交换黑色棋子看成一个流流动的过程,流从一个节点流向另一个节点就是交换两个节点,然后把一个位置拆成两个点限制流量,然后就有了这样的建图方法 S向所有初始是黑 ...
随机推荐
- spring-boot、mybatis整合
一.MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以使用简单的 X ...
- springBoot -webSocket 基于STOMP协议交互
浅谈WebSocket WebSocket是在HTML5基础上单个TCP连接上进行全双工通讯的协议,只要浏览器和服务器进行一次握手,就可以建立一条快速通道,两者就可以实现数据互传了.说白了,就是打破了 ...
- 安卓app连接CC2541-手机休眠后唤醒,通信不再成功
1. 现在遇到的问题,手机进入休眠状态后唤醒,APP软件和CC2541的通信不正常了,但是CC2541依然检测到时连接状态.如何解决这个问题?手机唤醒之后会重新创建活动? 2.Wakelock 锁机制 ...
- 一个只有十行的精简MVVM框架(下篇)
本文来自网易云社区. 让我们来加点互动 前面学生信息的身高的单位都是默认m,如果新增一个需求,要求学生的身高的单位可以在m和cm之间切换呢? 首先需要一个变量来保存度量单位,因此这里必须用一个新的Mo ...
- rn打包分析
rn打包原来是packager,后来独立出一个专门的打包工具metro,构建工具的大体思路跟前端构建工具差不多,都会有一个启动文件,然后根据模块依赖关系把对应文件找到. 开发中打包 在开发中打包,我们 ...
- spring java config 初探
Java Config 注解 spring java config作为同xml配置形式的另一种表达形式,使用的场景越来越多,在新版本的spring boot中 大量使用,今天我们来看下用到的主要注解有 ...
- jmeter链接数据库问题汇总
1.最新驱动下载: 驱动版本与mysql服务不兼容也是会报错的 下载地址:https://dev.mysql.com/downloads/connector/j/ 打开页面一直拉到页面底部,此处选择P ...
- 【转载】完全版线段树 by notonlysuccess大牛
原文出处:http://www.notonlysuccess.com/ 今晚上比赛就考到了 排兵布阵啊,难受. [完全版]线段树 很早前写的那篇线段树专辑至今一直是本博客阅读点击量最大的一片文章,当时 ...
- DataTable转Json,Json转DataTable
// 页面加载时 /// </summary> /// <param name="sender"></param> /// <param ...
- Python3 Tkinter-Listbox
1.创建 from tkinter import * root=Tk() lb=Listbox(root) for item in ['python','tkinter','widget']: lb. ...