Solution -「POI 2013」LAB-Maze
\(\mathscr{Description}\)
Link.
构造一个边平行与坐标轴, 顶点是整点, 相邻边互相垂直, 且逆时针遍历顶点时转向 (向左或向右) 符合给定字符串的不自交多边形.
顶点数 \(n\le10^5\).
\(\mathscr{Solution}\)
设 \(L\) 为左转数, \(R\) 为右转数, 显然, 有解的必要条件是 \(L-R=4\), 不然不可能逆时针转回起点.
接下来, 注意到相邻的一对 LR 或者 RL 一定体现为一个 "Z" 字形, 并不影响边的走向. 因此, 可以尝试通过不断去除这样的字符对来递归构造多边形.
研究递归的过程. 递归尽头一定是 LLLL, 此时我们新建一个矩形. 在回溯过程中, 我们要做的工作就是在一条直线中插入一个 "Z", 并合理调整坐标关系. 如图:

说着很简单, 怎么实现呢?
对于 LR 对的寻找, 可以用双向连边维护字符串上剩余字符的相邻关系, 然后把所有可能的 LR 和 RL 丢到队列里. 删掉 LR 之后再顺便检查一下新挨在一起的字符是否是 LR 或 RL.
对于 "合理调整坐标关系", 最好的安排方式自然是把 "Z" 的一竖变得 "很短很短", 短到不可能碰到任何其他边. 当然, 因为坐标需要是整数, 这不太可能. 不过, 我们在构造过程中并不关心一个点的具体坐标, 因而, 可以用两个双向链表维护 \(x\) 轴和 \(y\) 轴, 每个点的坐标仅仅用指向链表中元素的两个指针描述. 插入坐标通过链表插入 \(\mathcal O(1)\) 实现, 由于共用一个坐标的点一定只有两个, 所以也很容易将一个坐标 "分裂" 成两个相近坐标.
当然, 如果真的要写链表, 就需要精细地去记录当前直线的走向, 否则难以通过迭代器的关系判断左右上下. 一个懒人方法是用 Splay 维护坐标轴, 原理一样, 不过就可以直接用 rank 来描述两个坐标的相对关系啦. 取决于实现方式, 复杂度为 \(\mathcal O(n)\) 或者 \(\mathcal O(n\log n)\).
\(\mathscr{Code}\)
/*+Rainybunny+*/
#include <bits/stdc++.h>
#define rep(i, l, r) for (int i = l, rep##i = r; i <= rep##i; ++i)
#define per(i, r, l) for (int i = r, per##i = l; i >= per##i; --i)
const int MAXN = 1e5;
int n, pre[MAXN + 5], suf[MAXN + 5], xid[MAXN + 5], yid[MAXN + 5];
char tur[MAXN + 5];
bool vis[MAXN + 5];
std::queue<int> gap;
struct Splay {
static const int MAXND = 3e5;
int node, root, ch[MAXND][2], siz[MAXND], fa[MAXND], lab[MAXND];
Splay() {
newnd(), newnd(), root = fa[ch[1][1] = 2] = 1, siz[1] = 2;
}
inline int newnd() {
return siz[++node] = 1, node;
}
inline void pushup(const int u) {
siz[u] = siz[ch[u][0]] + siz[ch[u][1]] + 1;
}
inline void rotate(const int u) {
int v = fa[u], w = fa[v], k = ch[v][1] == u;
if ((fa[u] = w)) ch[w][ch[w][1] == v] = u;
if ((ch[v][k] = ch[u][!k])) fa[ch[v][k]] = v;
pushup(ch[fa[v] = u][!k] = v), pushup(u);
}
inline void splay(const int u, const int tar) {
for (int v, w; (v = fa[u]) != tar; rotate(u)) {
if ((w = fa[v]) != tar) {
rotate((v == ch[w][0]) == (u == ch[v][0]) ? v : u);
}
}
if (!tar) root = u;
}
inline int rank(const int u) {
splay(u, 0);
return siz[ch[u][0]] + 1;
}
inline int kth(int k) {
int u = root;
while (true) {
if (k <= siz[ch[u][0]]) u = ch[u][0];
else if (!(k -= siz[ch[u][0]] + 1)) return u;
else u = ch[u][1];
}
}
/** insert a node s.t. the rank of it is @p rk */
inline int insertAs(const int rk) {
assert(1 < rk && rk <= siz[root]);
int u = kth(rk - 1), v = kth(rk);
splay(u, 0), splay(v, u), assert(!ch[v][0]);
fa[ch[v][0] = newnd()] = v, pushup(v), pushup(u);
return ch[v][0];
}
inline void relable(const int u) {
if (!u) return ;
relable(ch[u][0]), lab[u] = ++lab[0], relable(ch[u][1]);
}
} X, Y;
inline void solve() {
if (gap.empty()) {
std::vector<int> rest;
rep (i, 0, n - 1) if (!vis[i]) rest.push_back(i);
assert(rest.size() == 4); // As a rectangle.
X.insertAs(2), X.insertAs(3);
Y.insertAs(2), Y.insertAs(3);
rep (i, 0, 3) {
xid[rest[i]] = !i || i == 3 ? 3 : 4;
yid[rest[i]] = i < 2 ? 3 : 4;
}
// printf("%d %d %d %d\n", rest[0], rest[1], rest[2], rest[3]);
return ;
}
int p = gap.front(), q = suf[p], s = pre[p], t = suf[q];
gap.pop();
if (vis[p] || vis[q] || tur[p] == tur[q]) return solve();
vis[p] = vis[q] = true, suf[s] = suf[q], pre[t] = pre[p];
assert(!vis[s] && !vis[t]);
if (tur[s] != tur[t]) gap.push(s);
solve();
// printf("%d --> %d - %d <-- %d\n", s, p, q, t);
if (yid[s] == yid[t]) {
int sxr = X.rank(xid[s]), txr = X.rank(xid[t]);
xid[p] = xid[q] = X.insertAs(std::min(sxr, txr) + 1);
yid[p] = yid[s];
if ((tur[p] == 'L') == (sxr < txr)) {
yid[q] = yid[t] = Y.insertAs(Y.rank(yid[p]) + 1);
} else {
yid[q] = yid[t] = Y.insertAs(Y.rank(yid[p]));
}
} else {
int syr = Y.rank(yid[s]), tyr = Y.rank(yid[t]);
yid[p] = yid[q] = Y.insertAs(std::min(syr, tyr) + 1);
xid[p] = xid[s];
if ((tur[p] == 'R') == (syr < tyr)) {
xid[q] = xid[t] = X.insertAs(X.rank(xid[p]) + 1);
} else {
xid[q] = xid[t] = X.insertAs(X.rank(xid[p]));
}
}
}
int main() {
// freopen("test.in", "r", stdin);
// freopen("test.out", "w", stdout);
scanf("%s", tur), n = strlen(tur);
std::replace(tur, tur + n, 'P', 'R'); // No Polish :(
int dlt = 0;
rep (i, 0, n - 1) dlt += tur[i] == 'L' ? 1 : -1;
if (dlt != 4) return puts("NIE"), 0;
rep (i, 0, n - 1) {
pre[i] = (i + n - 1) % n, suf[i] = (i + 1) % n;
if (tur[i] != tur[(i + 1) % n]) gap.push(i);
}
solve();
X.relable(X.root), Y.relable(Y.root);
rep (i, 0, n - 1) printf("%d %d\n", X.lab[xid[i]], Y.lab[yid[i]]);
return 0;
}
顺便, 给出自己码的一个 SPJ.
/*+Rainybunny+*/
#include "testlib.h"
#include <bits/stdc++.h>
#define MSG_OK \
"Congratulations~"
#define MSG_SOLUTION_EXISTS \
"You don't find a solution, but jury does."
#define MSG_NOT_PARALLEL \
"An edge isn't parallel to x-axis or y-axis (%d-th edge, 0-indexed)."
#define MSG_POINT_COINCIDE \
"Two nodes coincide in your solution."
#define MSG_NOT_VERTICAL \
"Two neighboring edges aren't vertical in your solution (%d-th edge and " \
"%d-th edges, 0-indexed)."
#define MSG_OUTSIDE_WALK \
"You're walking outside of your polygon."
#define MSG_WRONG_TURNING \
"A turning direction doesn't meet the requirement."
#define MSG_SELF_INTER \
"The polygon is self-intersecting in your solution."
#define rep(i, l, r) for (int i = l, rep##i = r; i <= rep##i; ++i)
#define per(i, r, l) for (int i = r, per##i = l; i >= per##i; --i)
typedef long long LL;
typedef std::pair<int, int> PII;
#define fi first
#define se second
const int MAXN = 1e5;
int n;
std::string tur;
struct Point {
int x, y;
inline Point operator - (const Point& u) const {
return { x - u.x, y - u.y };
}
inline LL operator * (const Point& u) const {
return 1ll * x * u.x + 1ll * y * u.y;
}
inline LL operator ^ (const Point& u) const {
return 1ll * x * u.y - 1ll * y * u.x;
}
inline bool operator < (const Point& u) const {
return x == u.x ? y < u.y : x < u.x;
}
};
Point pnt[MAXN + 5];
inline void initCheck() {
tur = inf.readToken(), n = int(tur.size());
std::rotate(tur.begin(), tur.end() - 1, tur.end());
std::string your = ouf.readToken(), jury = ans.readToken();
if (your == "NIE" && jury != "NIE") quitf(_wa, MSG_SOLUTION_EXISTS);
else if (your == "NIE") quitf(_ok, MSG_OK);
std::reverse(your.begin(), your.end());
for (char c: your) ouf.unreadChar(c);
rep (i, 0, n - 1) {
pnt[i].x = ouf.readInt(-1e9, 1e9);
pnt[i].y = ouf.readInt(-1e9, 1e9);
// fprintf(stderr, "? %d %d\n", pnt[i].x, pnt[i].y);
}
}
int fail[MAXN + 5];
std::string act;
std::set<Point> apr;
inline void turningCheck() {
rep (i, 0, n - 1) {
if ((pnt[i].x != pnt[(i + 1) % n].x)
&& (pnt[i].y != pnt[(i + 1) % n].y)) quitf(_wa, MSG_NOT_PARALLEL, i);
if (apr.count(pnt[i])) quitf(_wa, MSG_POINT_COINCIDE);
apr.insert(pnt[i]);
Point p(pnt[i] - pnt[(i + n - 1) % n]), q(pnt[(i + 1) % n] - pnt[i]);
// fprintf(stderr, "(%d,%d) -> (%d,%d)\n", p.x, p.y, q.x, q.y);
if (p * q) quitf(_wa, MSG_NOT_VERTICAL, (i + n - 1) % n, i);
act += (p ^ q) > 0 ? 'L' : 'P';
}
int dlt = 0;
for (int c: act) dlt += c == 'L' ? 1 : -1;
if (dlt == -4) {
// In this case, the answer should be "NIE", but the checker won't
// refuse to check your answer, knowing you must be wrong before.
quitf(_wa, MSG_OUTSIDE_WALK);
}
// std::cerr << "Your turning token: " << act << '\n';
fail[0] = -1;
for (int i = 1, j = -1; i < n; ++i) {
while (~j && tur[j + 1] != tur[i]) j = fail[j];
fail[i] = j += tur[j + 1] == tur[i];
}
for (int i = 0, j = -1; i < 2 * n; ++i) {
while (~j && tur[j + 1] != act[i % n]) j = fail[j];
if ((j += tur[j + 1] == act[i % n]) == n - 1) return ;
}
quitf(_wa, MSG_WRONG_TURNING);
}
int mx, dc[MAXN + 5];
std::vector<PII> evt[MAXN + 5], req[MAXN + 5];
std::set<int> cut;
inline void selfInterCheck() {
rep (i, 0, n - 1) dc[++mx] = pnt[i].x;
std::sort(dc + 1, dc + mx + 1);
mx = std::unique(dc + 1, dc + mx + 1) - dc - 1;
rep (i, 0, n - 1) {
pnt[i].x = std::lower_bound(dc + 1, dc + mx + 1, pnt[i].x) - dc;
}
rep (i, 0, n - 1) {
if (pnt[i].x == pnt[(i + 1) % n].x) {
int ya = pnt[i].y, yb = pnt[(i + 1) % n].y;
if (ya > yb) std::swap(ya, yb);
req[pnt[i].x].emplace_back(ya, yb);
} else {
int xa = pnt[i].x, xb = pnt[(i + 1) % n].x;
if (xa > xb) std::swap(xa, xb);
evt[xa].emplace_back(pnt[i].y, 1);
evt[xb].emplace_back(pnt[i].y, 0);
}
}
rep (i, 1, mx) {
for (const PII& p: evt[i]) if (!p.se) cut.erase(p.fi);
for (const PII& p: req[i]) {
auto&& it = cut.upper_bound(p.se);
if (it != cut.begin() && *--it >= p.fi) quitf(_wa, MSG_SELF_INTER);
}
for (const PII& p: evt[i]) if (p.se) {
if (cut.count(p.fi)) quitf(_wa, MSG_SELF_INTER);
cut.insert(p.fi);
}
for (const PII& p: evt[i]) if (!p.se) cut.insert(p.fi);
for (const PII& p: req[i]) if (p.fi + 2 <= p.se) {
auto&& it = cut.upper_bound(p.se - 1);
if (it != cut.begin() && *--it > p.fi) quitf(_wa, MSG_SELF_INTER);
}
for (const PII& p: evt[i]) if (!p.se) cut.erase(p.fi);
}
}
int main(int argc, char** argv) {
registerTestlibCmd(argc, argv);
initCheck();
turningCheck();
selfInterCheck();
quitf(_ok, MSG_OK);
return 0;
}
Solution -「POI 2013」LAB-Maze的更多相关文章
- Solution -「ZJOI 2013」「洛谷 P3337」防守战线
\(\mathcal{Description}\) Link. 有 \(n\) 个位置,从左至右编号 \(1\sim n\).在第 \(i\) 个位置放一座塔的代价为 \(c_i\),一个位置 ...
- Solution -「POI 2014」「洛谷 P5904」HOT-Hotels 加强版
\(\mathcal{Description}\) Link. 给定一棵 \(n\) 个点的树,求无序三元组 \((u,v,w)\) 的个数,满足其中任意两点树上距离相等. \(n\le1 ...
- Solution -「POI 2010」「洛谷 P3511」MOS-Bridges
\(\mathcal{Description}\) Link.(洛谷上这翻译真的一言难尽呐. 给定一个 \(n\) 个点 \(m\) 条边的无向图,一条边 \((u,v,a,b)\) 表示从 ...
- Solution -「POI 2011」「洛谷 P3527」MET-Meteors
\(\mathcal{Description}\) Link. 给定一个大小为 \(n\) 的环,每个结点有一个所属国家.\(k\) 次事件,每次对 \([l,r]\) 区间上的每个点点权加上 ...
- Solution -「ARC 104E」Random LIS
\(\mathcal{Description}\) Link. 给定整数序列 \(\{a_n\}\),对于整数序列 \(\{b_n\}\),\(b_i\) 在 \([1,a_i]\) 中等概率 ...
- Solution -「CTS 2019」「洛谷 P5404」氪金手游
\(\mathcal{Description}\) Link. 有 \(n\) 张卡牌,第 \(i\) 张的权值 \(w_i\in\{1,2,3\}\),且取值为 \(k\) 的概率正比于 \ ...
- Solution -「BZOJ 3812」主旋律
\(\mathcal{Description}\) Link. 给定含 \(n\) 个点 \(m\) 条边的简单有向图 \(G=(V,E)\),求 \(H=(V,E'\subseteq E)\ ...
- Solution -「CF 1342E」Placing Rooks
\(\mathcal{Description}\) Link. 在一个 \(n\times n\) 的国际象棋棋盘上摆 \(n\) 个车,求满足: 所有格子都可以被攻击到. 恰好存在 \(k\ ...
- Solution -「简单 DP」zxy 讲课记实
魔法题位面级乱杀. 「JOISC 2020 Day4」治疗计划 因为是不太聪明的 Joker,我就从头开始理思路了.中途也会说一些和 DP 算法本身有关的杂谈,给自己的冗长题解找借口. 首先,治疗方案 ...
- Solution -「基环树」做题记录
写的大多只是思路,比较简单的细节和证明过程就不放了,有需者自取. 基环树简介 简单说一说基环树吧.由名字扩展可得这是一类以环为基础的树(当然显然它不是树. 通常的表现形式是一棵树再加一条非树边,把图画 ...
随机推荐
- 实践出真知,小程序wepy,uni-app框架开发使用!
一.前提 目前我只使用过wepy和uni-app框架开发过小程序,着重比较这两个框架使用感受! 另外wepy框架已经不维护了,希望uni-app好好维护下去! wepy和uni-app都是类似于vue ...
- MongoDB聚合类操作
MongoDB中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果.有点类似sql语句中的 count(*) 语法:db.tablename.aggregat ...
- MySQL修改用户权限
最近测试中台的存储服务,涉及到MySQL用户操作的命令,记录一下. 1.查看所有用户 select user from mysql.user; 2.查看当前登陆用户 select user(); 3. ...
- STM32单片机 32.768Khz和8Khz晶振外部电容容值选取问题
一.前言 绘制STM32C8T6最小系统的晶振电路的时候,看到了并联在晶振两端的电容,就好奇具体的容值该怎么选取,故有了这篇博客. 二.外部晶振电路
- kubernetes组件大全
master节点组件 控制平面的组件我们会找一台单独的机器来部署,我们习惯上把部署控制平面组件的机器称为master节点,以下都会用master节点来代替控制平面这个概念,master节点的组件能够对 ...
- CodeForces Round 898 (div 4) H题解析
CodeForces Round 898 (div 4)H. Mad City 大致思路 对于有n条边和n个点,说明这个图里面只有一个环 并且两人同时开始和结束移动,所以可以得到当Valeri ...
- Vim基本使用指南
一般模式:移动光标的方法 h或 向左方向键(←)光标向左移动一个字符 j或 向下方向键(↓)光标向下移动一个字符 k或 向上方向键(↑)光标向上移动一个字符 l或 向右方向键(→)光标向右移动一个字符 ...
- 二、STM32F103C8T6-定时器
STM32F103C8T6 定时器概述 STM32F103C8T6 作为一款广泛使用的微控制器,内置多个定时器,能够支持多种计时和控制功能,如精确延时.脉冲宽度调制(PWM).捕获比较(Capture ...
- pip之加速
开发的时候, 经常会遇到使用pip安装某个包的时候很慢,甚至安装失败的情况, 当然可以设置--default-timeout=100方法来设置超时时间(默认是15秒),但是并不能解决实质问题.需要进行 ...
- js模拟点击下载文件到本地
function fake_click(obj) { var ev = document.createEvent("MouseEvents"); ev.initMouseEvent ...