【Codeforces512E_CF512E】Fox Polygon(构造)
题目
翻译
新冠肺炎时代,平时也不上网课,也懒得背单词,就翻译个 CF 题面装装还在学英语的样子 ……
(以上那句和这句题面里没有)
题目名:狐狸多边形
描述
狐狸塞尔设计了一个叫做「多边形」的解谜游戏!这个游戏与 \(n\) 条边的正多边形的三角剖分有关。目标是根据一些奇特的规则从一个三角剖分转换到另一个三角剖分。
正多边形的 三角剖分 定义为一个包含 \(n-3\) 条对角线的集合,满足没有两条对角线在多边形内部(不含边界)相交。
例如,游戏的初始状态是上图中的 (a) ,你的目标是上图中的 (c) 。每一步你可以在多边形中选一条对角线(不能选多边形的边),并 翻转 这条对角线。
如果你准备翻转对角线 \(a-b\) ,\(a-b\) 总是两个三角形的公共边,设这两个三角形是 \(a-b-c\) 和 \(a-b-d\) 。操作的结果是,对角线 \(a-b\) 被对角线 \(c-d\) 取代。可以轻易地证明,翻转操作后的对角线集合仍然是多边形的一个三角剖分。
所以对于上图中的情况,你可以先翻转对角线 \(6-3\) ,它会被对角线 \(2-4\) 取代。然后翻转对角线 \(6-4\) ,就能得到图 (c) 的结果。
塞尔刚刚证明了对于任意一种起始和目标状态,这个游戏都有解。她希望你在不超过 \(20000\) 步内解决任意一个满足 \(n\leq 1000\) 的谜题。
输入
第一行包含一个整数 \(n(4\leq n\leq 1000)\) ,正多边形的边的数量。
紧跟着包含两组 \((n-3)\) 行,分别描述初始三角剖分和目标三角剖分。
每一个三角剖分的描述由 \((n-3)\) 行组成。每一行包含两个整数 \(a_i\) 和 \(b_i(1\leq a_i,b_i\leq n)\) ,描述一条对角线 \(a_i-b_i\) 。
保证初始和目标三角剖分都是正确的(即两个三角剖分中都没有两条边在多边形内部相交)。
输出
首先,输出整数 \(k(0\leq k\leq 20000)\) :步数。
然后输出 \(k\) 行,每一行包含两个整数 \(a_i\) 和 \(b_i\) :第 \(i\) 步你将翻转的对角线的两个端点。你可以以任意顺序输出 \(a_i\) 和 \(b_i\) 。
如果有多种可能的解,请任意输出一种。
分析
我用的是题解评论区的做法,题解的做法实在太麻烦了(并且一般人都想不到吧 …… )。评论区的做法思路清晰,解法自然,码量适中。
显然这个操作是可逆的。即如果知道了一种从 \(A\) 到 \(B\) 的方法,那么倒着做一遍就可以从 \(B\) 到 \(A\) 。那么找一个特殊状态 \(S\) ,分别算出从初始状态和目标状态到 \(S\) 的方案,把前者和后者的逆序拼接起来就是最终方案。方便起见,把 \(S\) 定为由从 \(1\) 出发的 \(n-3\) 条对角线组成的集合。现在的问题变成了如何从一个任意状态变换到这种状态。
方案还是比较好构造的。考虑从 \(1\) 号点发出的边(包括所有从 \(1\) 发出的对角线和 \(1-2\) 、 \(1-(n-1)\) 这两条边,因此至少存在两条)中相邻的两条 \(1-a\) 和 \(1-b\) ,且 \(a\) 和 \(b\) 不是连续的两个点(如果不存在说明已经是状态 \(S\) 了),那么一定存在 \(a-b\) 这条边,否则不是一个三角剖分。翻转 \(a-b\) 这条边,得到 \(1-c\) ,其中 \(c\) 是 \(a\) 和 \(b\) 之间的一个特定的点。这样,从 \(1\) 出发的对角线就增加了一条。综上所述,任意一种状态最多操作 \(n-3\) 次就能到达 \(S\) ,因此这样操作的答案上界是 \(2n-6\) ,可以通过。具体实现细节详见代码。
代码
注意计算目标状态到 \(S\) 时应该记录的是变换后得到的边而不是变换前的边。
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cctype>
#include <set>
using namespace std;
namespace zyt
{
template<typename T>
inline bool read(T &x)
{
char c;
bool f = false;
x = 0;
do
c = getchar();
while (c != EOF && c != '-' && !isdigit(c));
if (c == EOF)
return false;
if (c == '-')
f = true, c = getchar();
do
x = x * 10 + c - '0', c = getchar();
while (isdigit(c));
if (f)
x = -x;
return true;
}
template<typename T>
inline void write(T x)
{
static char buf[20];
char *pos = buf;
if (x < 0)
putchar('-'), x = -x;
do
*pos++ = x % 10 + '0';
while (x /= 10);
while (pos > buf)
putchar(*--pos);
}
const int N = 1e3 + 10;
typedef pair<int, int> pii;
int n, anscnt, buf[N];
set<int> s[N];
pii ans[N << 1];
void solve(const int l, const int r, const bool flag)
{
if (r == l + 1)
return;
int tmp;
s[1].insert(tmp = *--s[l].lower_bound(r)), s[tmp].insert(1);
s[l].erase(r), s[r].erase(l);
ans[anscnt++] = (flag ? pii(l, r) : pii(1, tmp));
solve(l, tmp, flag), solve(tmp, r, flag);
}
int work()
{
read(n);
for (int i = 2; i < n; i++)
s[i].insert(i + 1), s[i].insert(i - 1);
s[1].insert(2), s[1].insert(n);
s[n].insert(1), s[n].insert(n - 1);
for (int i = 1; i <= n - 3; i++)
{
int a, b;
read(a), read(b);
s[a].insert(b), s[b].insert(a);
}
int cnt = 0;
for (set<int>::iterator it = s[1].begin(); it != s[1].end(); it++)
buf[cnt++] = *it;
for (int i = 1; i < cnt; i++)
solve(buf[i - 1], buf[i], true);
int tmp = anscnt;
for (int i = 1; i <= n; i++)
s[i].clear();
for (int i = 2; i < n; i++)
s[i].insert(i + 1), s[i].insert(i - 1);
s[1].insert(2), s[1].insert(n);
s[n].insert(1), s[n].insert(n - 1);
for (int i = 1; i <= n - 3; i++)
{
int a, b;
read(a), read(b);
s[a].insert(b), s[b].insert(a);
}
cnt = 0;
for (set<int>::iterator it = s[1].begin(); it != s[1].end(); it++)
buf[cnt++] = *it;
for (int i = 1; i < cnt; i++)
solve(buf[i - 1], buf[i], false);
reverse(ans + tmp, ans + anscnt);
write(anscnt), putchar('\n');
for (int i = 0; i < anscnt; i++)
write(ans[i].first), putchar(' '), write(ans[i].second), putchar('\n');
return 0;
}
}
int main()
{
return zyt::work();
}