[luoguT30208]太极剑
[luoguT30208]太极剑
试题描述
在学习太极之后,Bob 要求 Alice 教他太极剑。Alice 告诉他首先需要通过一项基本剑术测试。测试要求 Bob 尽可能快地切断 \(n\) 根绳子。
所有绳子的端点两两不同,所以共有 \(2n\) 个端点。这些端点被捆在一个圆上,等距离分布。我们把这些端点按顺时针方向编号为 \(1\) 到 \(2n\) 。
Bob 每次切割的轨迹是一条直线,可以将所有与这条直线相交的绳子切断,他想知道至少多少次可以切断所有的绳子。
输入
第一行一个整数 \(n(1 \leq n \leq 2 \times 10^5)\),表示绳子的个数。
接下来 \(n\) 行,每行两个整数 \(a_i, b_i(1 \leq a_i, b_i \leq 2n, a_i \not= b_i)\),表示第 \(i\) 根绳子的两个端点的编号。
输出
一行一个整数,表示答案。
输入示例1
2
1 2
3 4
输出示例1
1
输入示例2
3
1 2
3 4
5 6
输出示例2
2
输入示例3
3
1 3
2 4
5 6
输出示例3
1
数据规模及约定
见“输入”
题解
首先这个问题等价于“在圆周上选择尽量少的偶数个点,使得相邻两个点之间没有同色的端点,定义第 \(i\) 条线段的两个端点 \(a_i\) 和 \(b_i\) 的颜色为 \(i\)”。
首先证明“若存在 \(k\) 条直线的划分方案,则存在 \(2k\) 个点的划分方案”。这 \(k\) 条直线与圆的交点就是一个划分方案,假设相邻两个点之间有同色点,则证明某条线段没有被直线劈开。
然后证明“若存在 \(2k\) 个点的划分方案,则存在 \(k\) 调直线的划分方案”。构造方法就是每个点朝往后数 \(k\) 个点的那个点连一条直线,这样保证每一段弧都“封闭”在直线围成的一个平面区域内。
于是这两个问题就等价了。
后者显然会非常好做。考虑暴力枚举第一个划分点,那么这个圆就展开成了一条链,那么我们的目标就是用尽量少的首尾相接的区间覆盖整个链,使得每个区间内没有同色点对。显然只能贪心跳。
现在我们可以不用枚举所有的起点,考虑圆上最短的线段一定是要被切开的,那么最短的线段所覆盖的那个较短的弧上一定有一个划分点,那么在这个弧上枚举起点即可。假定这个弧上有 \(d\) 段小弧(就是两个端点之间的弧),那么我们每跳一次,显然能够至少跳 \(d\) 步,如果跳不了则说明 \(d\) 步内有同色点,那么这对同色点对应的线段就比我们最初选的那条线段更短了,与“最短”的假设矛盾。由此证明跳至多 \(\left \lfloor \frac{n}{d} \right \rfloor\) 步即可。复杂度就是线性的了(当然你要预处理每个点能跳到哪里)。
#include <bits/stdc++.h>
using namespace std;
#define rep(i, s, t) for(int i = (s), mi = (t); i <= mi; i++)
#define dwn(i, s, t) for(int i = (s), mi = (t); i >= mi; i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
#define maxn 800010
#define oo 2147483647
int n, col[maxn], nxt[maxn];
bool ncol[maxn];
int cdist(int a, int b) {
if(a > b) swap(a, b);
return min(b - a, a - 1 + (n >> 1) - b + 1);
}
int ans;
void solve(int sl, int sr) {
rep(s, sl, sr) {
int u = s, cnt = 0;
while(u < s + (n >> 1)) cnt++, u = nxt[u];
ans = min(ans, cnt + 1 >> 1);
}
return ;
}
int main() {
n = read() << 2;
int bestA = -1, bestB = -1;
rep(i, 1, n >> 2) {
int a = read(), b = read();
col[a] = col[b] = col[a+(n>>1)] = col[b+(n>>1)] = i;
if(bestA < 0 || cdist(a, b) < cdist(bestA, bestB)) bestA = a, bestB = b;
}
int rgt = 1;
rep(i, 1, n) {
while(rgt <= n && !ncol[col[rgt]]) ncol[col[rgt++]] = 1;
nxt[i] = rgt;
ncol[col[i]] = 0;
}
ans = oo;
if(bestA > bestB) swap(bestA, bestB);
if(bestB - bestA < bestA - 1 + (n >> 1) - bestB + 1) solve(bestA + 1, bestB);
else solve(1, bestA), solve(bestB + 1, n >> 1);
printf("%d\n", ans);
return 0;
}