题目
题意概要
把一个 n × m n\times m n×m 的方格图中填满 /
和 \
,规定第 i i i 行、第 i i i 列分别需要恰好有 r i r_i ri、 c i c_i ci 个 /
,并且不能形成正方形。
求任意一组方案。
数据范围与提示
n , m ⩽ 20 n,m\leqslant 20 n,m⩽20 。
思路
首先,贪心地用较大的 r i r_i ri 填较大的 c i c_i ci,可以找到一组解。但是可能有正方形,怎么办呢?
考虑一个简单的方法,就是将正方形的左右 “尖角” 的状态翻转,如图
/...\ --> \.../
\.../ /...\
你发现,这等价于左边的 /
下降而右边的 \
上升。于是我们定义势能 Φ = ∑ ( i , j ) = ‘ / " i ( m − j ) \Phi=\sum_{(i,j)=`/"}i(m-j) Φ=∑(i,j)=‘/"i(m−j),很容易发现势能必然减小。而势能最初是 O ( n 4 ) \mathcal O(n^4) O(n4) 的,所以最多进行这么多次调整。而且,有了这个势能,根本不需要判断是否存在正方形。
每次调整的时候,直接扫描相邻的两行,将所有这样的 “尖角” 翻转。这样一次是 O ( n 2 ) \mathcal O(n^2) O(n2) 的,复杂度就是 O ( n 6 ) \mathcal O(n^6) O(n6) 的。
还有更好的做法:直接求出 Φ \Phi Φ 最小的解。显然存在正方形时,则存在 Φ \Phi Φ 更小的解,所以 Φ \Phi Φ 最小时必然无正方形。直接用费用流,费用就是势能 i ( m − j ) i(m-j) i(m−j) 呗。时间复杂度 O ( n 4 log n ) \mathcal O(n^4\log n) O(n4logn) 。
代码
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <queue>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
int a = 0, c = getchar(), f = 1;
for(; !isdigit(c); c=getchar())
if(c == '-') f = -f;
for(; isdigit(c); c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int MAXN = 22;
int n, m, a[MAXN][MAXN];
priority_queue< pair<int,int> > pq;
int r[MAXN], c[MAXN], id[MAXN];
bool cmp(const int &_x,const int &_y){
return r[_x] < r[_y];
}
queue<int> bin;
int main(){
n = readint(), m = readint();
rep(i,1,n) r[id[i]=i] = readint();
std::sort(id+1,id+n+1,cmp);
rep(i,1,m){
c[i] = readint();
pq.push(make_pair(c[i],i));
}
drep(i,n,1){
while(r[id[i]] --){
if(pq.empty()) // nothing to choose
return puts("IMPOSSIBLE"), 0;
int v = pq.top().second; pq.pop();
a[id[i]][v] = 1;
if(-- c[v]) bin.push(v);
}
for(; !bin.empty(); bin.pop()){
int x = bin.front();
pq.push(make_pair(c[x],x));
}
}
rep(j,1,m) if(c[j] != 0)
return puts("IMPOSSIBLE"), 0;
for(bool ok=false; !ok&&(ok=true); )
rep(i,1,n-1) for(int j=1,k=m; j<k; ){
while(j <= m && (a[i][j] == a[i+1][j] || !a[i][j])) ++ j;
while(k && (a[i][k] == a[i+1][k] || a[i][k])) -- k;
if(j < k){
a[i+1][j] = a[i][k] = 1;
a[i][j] = a[i+1][k] = 0, ok = false;
}
}
puts("POSSIBLE");
rep(i,1,n){
rep(j,1,m)
if(a[i][j]) putchar('/');
else putchar('\\');
putchar('\n');
}
return 0;
}
后记
没能构造出 O ( n 4 ) \mathcal O(n^4) O(n4) 次翻转,最多做到 O ( n 3 ) \mathcal O(n^3) O(n3),且最多跑 O ( n 2 ) \mathcal O(n^2) O(n2) 轮。无法证明。毕竟本来就是网络流,网络流的复杂度是玄学……