[2017清华集训]无限之环

嘟嘟嘟


这算是我做过的最神的一道网络流题了。
话说有人能想到这题用费用流吗


某神仙说,看到网格图,就能想到黑白染色(我就想不到)。那么假设我们现在想到了。
然后用1个流量代表一个管道接口,那么存在解得条件必定是总流量等于接口数除以2。


先想一下不能转动的情况:容易想到拆点,把一个点分别拆成这个点的上、右、下、左接口四个点。然后先把整个图连通,即\((i, j)\)的右接口连\((i, j + 1)\)的左接口,\((i, j)\)的下接口连\((i + 1, j)\)的上接口。注意,因为是黑白染色,源点连向黑点,白点连向汇点,所以如果连边的时候出现白点连黑点,要反过来。
然后对于每一个黑点,从源点向他的每一个存在的接口连一条容量为1,费用为0的边;对于每一个白点,其当前存在的接口向汇点连一条\((1, 0)\)的边。
这样这个不能转动的图就构造完了。


那么每一个点能转动该怎么办?连边的确很巧妙。
通过观察发现,能转动的管道无非三种:


这种情况是最简单的。
向右转90度,那么就从他的上接口向右接口连一条\((1, 1)\)的边;向左转90度,就从上接口向左接口连一条\((1, 1)\)的边;转180度,就从上接口向下接口连一条\((1, 2)\)的边。


那么这种情况也同理。向右转90度,发现向右的接口还在,那么只用从上接口向下接口连一条\((1, 1)\)的边;向左转90度,就从右接口向左接口连一条\((1, 1)\)的边;180度不用再连边了,因为上述两种情况合起来就是转180度!


后一种情况想想也就出来了:从左、有接口向下接口连一条\((1, 1)\)的边,从上接口向下接口连一条\((1, 2)\)的边。

然后这题就是这么回事了!
不过建图代码不是那么好些,因为每一种情况自身还会旋转,所以维护一个变量turn,表示他已经转了几次,这样向右向左向下转其实就是turn+1或是turn+2了。于是我们就成功避免了16种情况的大分类讨论。

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cctype>
#include<vector>
#include<stack>
#include<queue>
using namespace std;
#define enter puts("") 
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
const int maxN = 1e4 + 5;
const int maxe = 4e6 + 5;
inline ll read()
{
  ll ans = 0;
  char ch = getchar(), last = ' ';
  while(!isdigit(ch)) last = ch, ch = getchar();
  while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
  if(last == '-') ans = -ans;
  return ans;
}
inline void write(ll x)
{
  if(x < 0) x = -x, putchar('-');
  if(x >= 10) write(x / 10);
  putchar(x % 10 + '0');
}

int n, m, s, t, TOT = 0;

struct Edge
{
  int nxt, from, to, cap, cos;
}e[maxe];
int head[maxN], ecnt = -1;
In void addEdge(int x, int y, int w, int c, int flg)  //black to white
{
  if(y != t && flg) swap(x, y);  //if x is white, swap x and y
  e[++ecnt] = (Edge){head[x], x, y, w, c};
  head[x] = ecnt;
  e[++ecnt] = (Edge){head[y], y, x, 0, -c};
  head[y] = ecnt;
}

In int num(int x, int y, int z)
{
  return ((m * (x - 1) + y - 1) << 2) + z + 1;
}
#define U(x, y) num(x, y, 0)
#define R(x, y) num(x, y, 1)
#define D(x, y) num(x, y, 2)
#define L(x, y) num(x, y, 3)

In void build(int x, int y, int z, int flg)
{
  int tp = 0;
  for(int i = 0; i < 4; ++i)  //to start/end
    if((z >> i) & 1)
      {
	++TOT; ++tp;
	if((x + y) & 1) addEdge(num(x, y, i), t, 1, 0, flg);
	else addEdge(s, num(x, y, i), 1, 0, flg);
      }
  int turn = 0;
  if(tp == 1)
    {
      if(z == 2) turn = 1;
      if(z == 4) turn = 2;
      if(z == 8) turn = 3;
      int u = num(x, y, turn);
      addEdge(u, num(x, y, (turn + 3) % 4), 1, 1, flg);
      addEdge(u, num(x, y, (turn + 1) % 4), 1, 1, flg);
      addEdge(u, num(x, y, (turn + 2) % 4), 1, 2, flg);
    }
  if(tp == 2)
    {
      if(z == 6) turn = 1;
      if(z == 12) turn = 2;
      if(z == 9) turn = 3;
      if(z != 5 && z != 10)
	{
	  int u = num(x, y, turn), v = num(x, y, (turn + 1) % 4);
	  addEdge(u, num(x, y, (turn + 2) % 4), 1, 1, flg);
	  addEdge(v, num(x, y, (turn + 3) % 4), 1, 1, flg);
	}
    }
  if(tp == 3)
    {
      if(z == 7) turn = 1;
      if(z == 14) turn = 2;
      if(z == 13) turn = 3;
      int v = num(x, y, (turn + 2) % 4);
      addEdge(num(x, y, turn), v, 1, 2, flg);
      addEdge(num(x, y, (turn + 1) % 4), v, 1, 1, flg);
      addEdge(num(x, y, (turn + 3) % 4), v, 1, 1, flg);
    }
}

bool in[maxN];
int dis[maxN], pre[maxN], flow[maxN];
In bool spfa()
{
  Mem(dis, 0x3f); Mem(in, 0);
  queue<int> q; q.push(s);
  in[s] = 1; dis[s] = 0; flow[s] = INF;
  while(!q.empty())
    {
      int now = q.front(); q.pop(); in[now] = 0;
      for(int i = head[now], v; ~i; i = e[i].nxt)
	{
	  v = e[i].to;
	  if(e[i].cap && dis[now] + e[i].cos < dis[v])
	    {
	      dis[v] = dis[now] + e[i].cos;
	      pre[v] = i;
	      flow[v] = min(flow[now], e[i].cap);
	      if(!in[v]) in[v] = 1, q.push(v);
	    }
	}
    }
  return dis[t] ^ INF;
}
int maxFlow = 0, minCost = 0;
In void update()
{
  int x = t;
  while(x ^ s)
    {
      int i = pre[x];
      e[i].cap -= flow[t];
      e[i ^ 1].cap += flow[t];
      x = e[i].from;
    }
  maxFlow += flow[t];
  minCost += flow[t] * dis[t];
}
In void MCMF()
{
  while(spfa()) update();
}

int main()
{
  Mem(head, -1);
  n = read(), m = read();
  s = 0, t = n * m * 4 + 1;
  for(int i = 1; i <= n; ++i)
    for(int j = 1; j <= m; ++j)
      {
	int x = read();
	build(i, j, x, (i + j) & 1);
      }
  for(int i = 1; i <= n; ++i)  //相邻接口连边
    for(int j = 1; j <= m; ++j)
      {
	if(j < m) addEdge(R(i, j), L(i, j + 1), 1, 0, (i + j) & 1);
	if(i < n) addEdge(D(i, j), U(i + 1, j), 1, 0, (i + j) & 1);
      }
  MCMF();
  write(maxFlow == (TOT >> 1) ? minCost : -1), enter;
  return 0;
}
posted @ 2019-03-13 20:21  mrclr  阅读(515)  评论(0编辑  收藏  举报