• [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;
    }
    
  • 相关阅读:
    Windows环境下 配置memcached (php)
    谈谈我是怎么学习PHP的(一)
    重编译Linux命令源代码
    php面向对象学习
    Windows文件系统漏洞
    十分钟能学会的框架,MVC+20个常用函数
    linux系统安装软件方法大全
    oracle基本操作
    MySQL的limit查询优化
    C# winform 可视化操作 Excel文件并读取数据
  • 原文地址:https://www.cnblogs.com/mrclr/p/10525969.html
Copyright © 2020-2023  润新知