1902: #2321. 「清华集训 2017」无限之环

文章探讨了如何通过最少的旋转操作消除游戏InfinityLoop中的漏水点,涉及图论算法如MCMF和SPFA的应用。
摘要由CSDN通过智能技术生成

题目描述

曾经有一款流行的游戏,叫做 Infinity Loop,先来简单的介绍一下这个游戏:

游戏在一个 n×mn \times mn×m 的网格状棋盘上进行,其中有些小方格中会有水管,水管可能在方格某些方向的边界的中点有接口,所有水管的粗细都相同,所以如果两个相邻方格的公共边界的中点都有接头,那么可以看作这两个接头互相连接。水管有以下 151515 种形状:

Screen Shot 2017-12-04 at 18.13.48.png

 

Screen Shot 2017-12-04 at 18.13.55.png

游戏开始时,棋盘中水管可能存在漏水的地方。

形式化地:如果存在某个接头,没有和其它接头相连接,那么它就是一个漏水的地方。

玩家可以进行一种操作:选定一个含有非直线型水管的方格,将其中的水管绕方格中心顺时针或逆时针旋转 909090 度。

直线型水管是指左图里中间一行的两种水管。

现给出一个初始局面,请问最少进行多少次操作可以使棋盘上不存在漏水的地方。

输入

第一行两个正整数 n,mn,mn,m,代表网格的大小。

接下来 nnn 行每行 mmm 个数,每个数是 [0,15][0,15][0,15] 中的一个,你可以将其看作一个 444 位的二进制数,从低到高每一位分别代表初始局面中这个格子上、右、下、左方向上是否有 水管接头。

特别地,如果这个数是 000,则意味着这个位置没有水管。

比如 3(0011(2))3(0011_{(2)})3(0011(2)) 代表上和右有接头,也就是一个 L 型,而 12(1100(2))12(1100_{(2)})12(1100(2)) 代表下和左有接头,也就是将 L 型旋转 180180180 度。

输出

输出共一行,表示最少操作次数。如果无法达成目标,输出 −1-1−1.

样例输入 

2 3
3 14 12
3 11 12

样例输出 

2

提示

测试点编号n×mn\times mn×m 的范围特殊约定
1n×m≤16n\times m \le 16n×m≤16无特殊要求
2
3n×m≤2000n \times m \le 2000n×m≤2000min(n,m)≤15\min(n,m) \le 15min(n,m)≤15
4
5
6
7
8
9无特殊要求
10
11
12
13
14
15
16
17
18
19
20

 

#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;
}

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值