[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向所有初始是黑 ...
随机推荐
- 用wireshark查看 tcpdump 抓取的mysql交互数据
用tcpdump 抓取 mysql客户端与服务器端的交互 1开启tcpdump tcpdump -i eth0 -s 3000 port 3306 -w ~/sql.pcap 先故意输入一个错误的密 ...
- dubbo入门(一)
1.简介 Dubbo由阿里巴巴开源,是一个分布式服务框架,致力于提供高性能和透明化的RPC(远程过程调用)远程服务调用方案,以及SOA服务治理方案.如果没有分布式的需求,Dbubbo是不需要的,其本质 ...
- 使用redux-actions优化actions管理
redux-actions的api很少,有三个createAction(s) handleASction(s) combineActions 主要用到createAction去统一管理actio ...
- hdu1312Red and Black(迷宫dfs,一遍)
Red and Black Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Tot ...
- web自动化测试框架总结
web自动化测试框架总结: https://www.processon.com/mindmap/5bdab924e4b0878bf41e9e09
- tpo-08 C2 A strategy for attracting customers
第 1 段 1.Listen to a conversation between a student and a business professor. 听一段学生和教授的对话. 第 2 段 1.So ...
- webservice调用天气
class WebServiceHelper { /// <summary> /// 动态调用WebService /// </summary> /// <param n ...
- JVM之G1收集器
Garbage-First,面向服务端的垃圾收集器. 并行与并发:充分利用多核环境减少停顿时间, 分代收集:不需要配合其它收集器 空间整合:整体上看属于标记整理算法,局部(region之间)数据复制算 ...
- 【python 3.6】从网站抓图并存放到本地路径
#!/usr/bin/python # -*- coding: UTF-8 -*- _author_ = 'BH8ANK' import urllib.request import re import ...
- leetcode个人题解——#19 Remove Nth Node From End of List
思路:设置两个指针,其中第二个指针比第一个延迟n个元素,这样,当第二个指针遍历到指针尾部时,对第一个指针进行删除操作. 当然,这题要注意一些边界值,比如输入[1,2] n=2时如果按照思路走会指向未分 ...