Codeforces 1428D Bouncing Boomerangs
\(\mathcal{Translate}\)
在一个 \(n\times n\) 的网格图中,在下面的每一列都会射出一个飞镖,在 \(n\times n\) 的网格图中会有若干个目标,如果飞镖遇到目标则顺时针旋转 \(90^{\circ}\) 后继续飞行直到飞出网格图。
规定在每一行每一列最多有 \(2\) 个目标,第 \(i\) 行的飞镖会旋转 \(a_i\) 次,现给定 \(a_i\), 构造出一组合法的解(确定网格图中的目标的个数和位置)。
\(n\leq 10^5,0\leq a_i\leq 3\)
凉心构造题。
\(\mathcal{Solution}\)
对于 \(a_i=0\),忽略这个地方,不会对答案产生什么影响。
先处理 \(a_i=1\) 的情况,因为 \(a_i=2,3\) 会依靠另一列来进行第二、三次反弹,而 \(a_i=1\) 不需要考虑,直接出去占用一行就可以。怎样占用最优?因为其它 \(3\) 匹配这个 \(1\) 进行三次反弹的时候需要上面多开一行,所以从下往上依次占用。
从左往右找 \(a_i=2\),之后再找到右边第一个没有被前面的 \(2\) 匹配的 \(1\),匹配到一起,也就是利用这个 \(1\) 的这一列目标进行第二次反弹。
为什么找右边第一个 \(1\) ? 因为 \(2\) 需要右边的 \(1\) 来反弹,贪心一下是选尽量靠左的更优,把尽量靠右让给右边的 \(2\)。
为什么先找 \(a_i=2\)?因为 \(a_i=3\) 可以依靠其他的 \(1,2,3\) 进行 \(3\) 次反转。
最后就是 \(a_i=3\) 的情况,它可以有三种方法来进行三次反弹:
-
右边没有被其它 \(2,3\) 匹配的 \(1\)。
-
右边没有被其它 \(3\) 匹配的 \(2\)。
-
右边没有被其它 \(3\) 匹配的 \(3\)。
因为可以从右边的 \(3\) 来反弹,所以这里 \(3\) 从后往前匹配找。
\(a_i=2,3\) 去匹配的时候采用双指针扫,一个扫 \(a_i=2,3\),一个扫哪一位可以匹配。如果没扫到就是无解。
\(\mathcal{Code}\)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define ll long long
#define re register
//#define int long long
inline int Max(int x, int y) { return x > y ? x : y; }
inline int Min(int x, int y) { return x < y ? x : y; }
inline int Abs(int x) { return x < 0 ? ~x + 1 : x; }
inline int read() {
int r = 0;
bool w = 0;
char ch = getchar();
while(ch < '0' || ch > '9') {
if(ch == '-') w = 1;
ch = getchar();
}
while(ch >= '0' && ch <= '9') {
r = (r << 3) + (r << 1) + (ch ^ 48);
ch = getchar();
}
return w ? ~r + 1 : r;
}
//#undef int
const int N = 300010;
bool f;
int n, a[N], cnt, ansx[N], ansy[N];
bool vis[N];
int row[N], rowcnt;
signed main() {
n = read();
for(int i = 1; i <= n; ++i) a[i] = read();
//先把所有的1一行一行开
for(int i = 1; i <= n; ++i)
if(a[i] == 1) ++cnt, ansx[cnt] = i, ansy[cnt] = ++rowcnt, row[i] = rowcnt;
int pos = 1;
//2和右边第一个1匹配
for(int i = 1; i <= n; ++i) {
if(a[i] == 2) {
pos = Max(pos, i);
while(a[pos] != 1 && pos <= n) ++pos;
if(pos == n + 1) {
puts("-1");
return 0;
}
++cnt;
ansx[cnt] = i, row[i] = ansy[cnt] = row[pos];
vis[pos] = 1;
++pos;
}
}
//3和右边三种情况匹配
pos = n;
for(int i = n; i >= 1; --i) {
if(a[i] == 3) {
while(!((a[pos] == 1 && !vis[pos]) || a[pos] == 2 || (a[pos] == 3 && row[pos])) && pos >= i) --pos;
if(pos == i - 1) {
puts("-1");
return 0;
}
++cnt; ansx[cnt] = i, ansy[cnt] = ++rowcnt;
++cnt; ansx[cnt] = pos, ansy[cnt] = rowcnt;
row[i] = rowcnt;
--pos;
}
}
//无解
if(cnt > n << 1 || rowcnt > n) {
puts("-1");
return 0;
}
printf("%d\n", cnt);
for(int i = 1; i <= cnt; ++i) printf("%d %d\n", n - ansy[i] + 1, ansx[i]);
return 0;
}