没有传送门
题目大意
有一个 的 矩阵,矩阵的边界值已知(即上下左右四条边界 个格子的值已知),你要在其余格子里填上 或 。每个没有值的格子上一个大写字母,要求相邻的字母相同的格子的值也相同。注意:如果两个格子字母相同但是不相邻,那么没有限制;如果两个格子相邻但是字母不同,也没有限制。
要求最后填写后的矩阵不能包含:
以及
以及
以及
。
Limited Constraint: 。
Special Instance:相邻格子的字母一定不同。(即随便填 )
思路
考虑 Limited Constraint,由于只有 个格子要填,因此直接搜索。一旦出现了不能出现的子矩阵就退出,这样可以很快出解。(但显然不可能加神级剪枝)
直接考虑 Special Instance 似乎没有突破点。我们考虑一下类似模型。还记得 APIO 某道题要求每个 的子矩形有奇数个 吗?那道题我们将个数转成了异或,这道题也能这么做吗?
由于还存在其它子矩形有奇数个 ,因此不能如法炮制。但是这给了我们一个思路:找到子矩形的特征,然后想办法把特征拿出来。
题目中要求相邻且字母相同的格子填的数也必须相同,我们可以考虑从相邻格是否相同出发。观察发现,在不能出现的四种子矩形中,相邻格1相同的数目要么是 ,要么是 。而对于剩下 种子矩形,我们可以发现相邻格相同的数目是 。
那么我们就从这里入手啦!现在我们想到要给格子染色,使得对于每个 的子矩形,都恰有 对相邻格相同。
怎么做呢?考虑网络流。我们从源点运送“相邻格相同的数目”,对于每个 的子矩形,我们为它们建一个结点,然后从源点向它们连一条容量为 的边,从它们向汇点连一条容量为 的边,那么有解当且仅当最大流满流。注意到,对于相邻的两个子矩形,如果相交的部分相同,那么这个“相邻格相同的数目”对这两个子矩形都有贡献。这又是一个“一对二”的模型(见我前面几篇题解),我们需要把所有 的子矩形分成两类,要求相邻的子矩形一定是不同的类型。显然可以按子矩形的横坐标加纵坐标的奇偶性分类。对于奇数类型的子矩形,我们从源点向它们连一条容量为 的边,对于偶数类型的子矩形,我们从它们向汇点连一条容量为 的边。对于相邻的子矩形,我们从奇数类型的子矩形向偶数类型的子矩形连一条容量为 的边,表示有一个共享的“相邻格相同的数目”(这里是“一对二”的体现)。那么有解当且仅当最大流满流。
如何满足某些相邻格必须相同的限制呢?显然对于这些相邻格,我们为它们对应的边设一个下限,跑有下界的最大流,有解当且仅当存在最大可行流满流。
如何满足边界已经确定的限制呢?发现我们如果要根据网络流构造方案,关键要看子矩形之间的边是否有流量,有流量就代表这相邻的两个格子填的数相同。发现除了四个角,边界上的数只会占用一个子矩形两个格子,如果相同,我们让流量减去相同的个数即可。
参考代码
不知道为什么有上下界的网络流过不了???所以写了个代替品。
不知道是不是上下界网络流写错了……QAQ
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <cassert>
#include <cctype>
#include <climits>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <stack>
#include <queue>
#include <deque>
#include <map>
#include <set>
#include <bitset>
#include <list>
#include <functional>
typedef long long LL;
typedef unsigned long long ULL;
using std::cin;
using std::cout;
using std::endl;
typedef LL INT_PUT;
INT_PUT readIn()
{
INT_PUT a = 0;
bool positive = true;
char ch = getchar();
while (!(std::isdigit(ch) || ch == '-')) ch = getchar();
if (ch == '-')
{
positive = false;
ch = getchar();
}
while (std::isdigit(ch))
{
(a *= 10) -= ch - '0';
ch = getchar();
}
return positive ? -a : a;
}
void printOut(INT_PUT x)
{
char buffer[20];
int length = 0;
if (x < 0) putchar('-');
else x = -x;
do buffer[length++] = -(x % 10) + '0'; while (x /= 10);
do putchar(buffer[--length]); while (length);
}
const int maxn = 35;
int n;
char rect[maxn][maxn];
#define RunInstance(x) delete new x
struct brute
{
int fill[maxn][maxn];
bool check(int x, int y)
{
if (fill[x - 1][y - 1] == 0 &&
fill[x - 1][y] == 0 &&
fill[x][y - 1] == 0 &&
fill[x][y] == 0)
return false;
if (fill[x - 1][y - 1] == 0 &&
fill[x - 1][y] == 1 &&
fill[x][y - 1] == 1 &&
fill[x][y] == 0)
return false;
if (fill[x - 1][y - 1] == 1 &&
fill[x - 1][y] == 0 &&
fill[x][y - 1] == 0 &&
fill[x][y] == 1)
return false;
if (fill[x - 1][y - 1] == 1 &&
fill[x - 1][y] == 1 &&
fill[x][y - 1] == 1 &&
fill[x][y] == 1)
return false;
return true;
}
bool bFound;
void search(int x, int y)
{
if (x == n)
{
if (check(n, n))
bFound = true;
return;
}
int newx = x;
int newy = y + 1;
if (newy == n)
{
newx++;
newy = 2;
}
if (rect[x][y] == rect[x - 1][y] &&
rect[x][y] == rect[x][y - 1])
{
if (fill[x - 1][y] != fill[x][y - 1])
return;
fill[x][y] = fill[x - 1][y];
if (check(x, y) && (y != n - 1 || check(x, n)) && (x != n - 1 || check(n, y)))
search(newx, newy);
}
else if (rect[x][y] == rect[x - 1][y])
{
fill[x][y] = fill[x - 1][y];
if (check(x, y) && (y != n - 1 || check(x, n)) && (x != n - 1 || check(n, y)))
search(newx, newy);
}
else if (rect[x][y] == rect[x][y - 1])
{
fill[x][y] = fill[x][y - 1];
if (check(x, y) && (y != n - 1 || check(x, n)) && (x != n - 1 || check(n, y)))
search(newx, newy);
}
else
{
fill[x][y] = 0;
if (check(x, y) && (y != n - 1 || check(x, n)) && (x != n - 1 || check(n, y)))
search(newx, newy);
if (bFound) return;
fill[x][y] = 1;
if (check(x, y) && (y != n - 1 || check(x, n)) && (x != n - 1 || check(n, y)))
search(newx, newy);
}
}
brute() : fill(), bFound()
{
for (int i = 1; i <= n; i++)
{
fill[1][i] = rect[1][i] - '0';
fill[i][1] = rect[i][1] - '0';
fill[n][i] = rect[n][i] - '0';
fill[i][n] = rect[i][n] - '0';
}
search(2, 2);
puts(bFound ? "YES" : "NO");
}
};
struct work
{
struct NetworkFlow
{
struct Edge
{
int from, to, cap, flow;
Edge() {}
Edge(int from, int to, int cap) : from(from), to(to), cap(cap), flow() {}
};
std::vector<Edge> edges;
std::vector<std::vector<int> > G;
void addEdge(int from, int to, int cap)
{
edges.push_back(Edge(from, to, cap));
edges.push_back(Edge(to, from, 0));
int i = edges.size();
G[from].push_back(i - 2);
G[to].push_back(i - 1);
}
int s, t;
std::vector<int> level;
std::vector<int> cur;
int Dinic(int node, int opt)
{
if (node == t || !opt) return opt;
int flow = 0;
for (int& i = cur[node]; i < G[node].size(); i++)
{
Edge& e = edges[G[node][i]];
int t;
if (level[node] + 1 == level[e.to] && e.flow < e.cap &&
(t = Dinic(e.to, std::min(opt, e.cap - e.flow))))
{
e.flow += t;
edges[G[node][i] ^ 1].flow -= t;
flow += t;
opt -= t;
if (!opt) break;
}
}
return flow;
}
struct Queue : private std::vector<int>
{
int head;
bool empty() { return head == size(); }
void clear() { head = 0; std::vector<int>::clear(); }
void push(int x) { push_back(x); }
void pop() { head++; }
int front() { return operator[](head); }
} q;
std::vector<bool> vis;
bool BFS()
{
q.clear();
level.resize(G.size());
vis.clear();
vis.resize(G.size());
level[s] = 0;
vis[s] = true;
q.push(s);
while (!q.empty())
{
int from = q.front();
q.pop();
for (int i = 0; i < G[from].size(); i++)
{
const Edge& e = edges[G[from][i]];
if (e.flow < e.cap && !vis[e.to])
{
vis[e.to] = true;
level[e.to] = level[e.from] + 1;
q.push(e.to);
}
}
}
return vis[t];
}
int maxFlow()
{
int flow = 0;
while (BFS())
{
cur.clear();
cur.resize(G.size());
flow += Dinic(s, INT_MAX);
}
return flow;
}
} nf;
int N;
int idx[maxn][maxn];
int color[maxn][maxn];
int cnt[maxn * maxn];
int countHelper(int x, int y)
{
int f = 0;
if (std::isdigit(rect[x - 1][y - 1]))
if (rect[x - 1][y - 1] == rect[x - 1][y])
f++;
if (std::isdigit(rect[x - 1][y]))
if (rect[x - 1][y] == rect[x][y])
f++;
if (std::isdigit(rect[x][y]))
if (rect[x][y] == rect[x][y - 1])
f++;
if (std::isdigit(rect[x][y - 1]))
if (rect[x][y - 1] == rect[x - 1][y - 1])
f++;
return f;
}
int cap[maxn][maxn];
work() : N(), idx(), cnt(), cap()
{
for (int i = 2; i <= n; i++)
for (int j = 2; j <= n; j++)
idx[i][j] = ++N;
color[1][2] = 1;
for (int i = 2; i <= n; i++)
{
color[i][2] = !color[i - 1][2];
for (int j = 3; j <= n; j++)
color[i][j] = !color[i][j - 1];
}
nf.G.resize(N + 2);
nf.s = 0;
nf.t = N + 1;
for (int i = 2; i <= n; i++)
for (int j = 2; j <= n; j++)
{
int c = countHelper(i, j);
cap[i][j] += 2 - c;
}
for (int i = 2; i <= n; i++)
{
for (int j = 2; j <= n; j++) if (!color[i][j])
{
if (i > 2)
{
if (rect[i - 1][j - 1] == rect[i - 1][j])
{
cap[i][j]--;
cap[i - 1][j]--;
}
else
nf.addEdge(idx[i][j], idx[i - 1][j], 1);
}
if (i < n)
{
if (rect[i][j - 1] == rect[i][j])
{
cap[i][j]--;
cap[i + 1][j]--;
}
else
nf.addEdge(idx[i][j], idx[i + 1][j], 1);
}
if (j > 2)
{
if (rect[i - 1][j - 1] == rect[i][j - 1])
{
cap[i][j]--;
cap[i][j - 1]--;
}
else
nf.addEdge(idx[i][j], idx[i][j - 1], 1);
}
if (j < n)
{
if (rect[i - 1][j] == rect[i][j])
{
cap[i][j]--;
cap[i][j + 1]--;
}
else
nf.addEdge(idx[i][j], idx[i][j + 1], 1);
}
}
}
int full1 = 0, full2 = 0;
for (int i = 2; i <= n; i++)
for (int j = 2; j <= n; j++)
if (color[i][j])
full1 += cap[i][j];
else
full2 += cap[i][j];
if (full1 != full2)
{
puts("NO");
return;
}
for (int i = 2; i <= n; i++)
for (int j = 2; j <= n; j++)
{
if (cap[i][j] < 0)
{
puts("NO");
return;
}
if (cap[i][j])
{
if (color[i][j])
nf.addEdge(idx[i][j], nf.t, cap[i][j]);
else
nf.addEdge(nf.s, idx[i][j], cap[i][j]);
}
}
if (nf.maxFlow() != full1)
puts("NO");
else
puts("YES");
}
};
void run()
{
n = readIn();
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
char ch = getchar();
while (!(std::isdigit(ch) || std::isalpha(ch))) ch = getchar();
rect[i][j] = ch;
}
}
RunInstance(work);
}
int main()
{
#ifndef LOCAL
freopen("color.in", "r", stdin);
freopen("color.out", "w", stdout);
#endif
run();
return 0;
}
总结
虽然说这道题有点难,但是这道题用的几个模型(比如
子矩形,网络流“一对二”模型)都是遇到过的。如果做过题并且看了题解就觉得不是特别难了,果然还是做题量才是王道?
Remark
复习一下有下界的网络流。
- 相邻格指:左上与右上,右上与右下,右下与左下,左下与左上,共四对。 ↩