题目描述
在学习太极之后,Bob 要求 Alice 教他太极剑。Alice 告诉他首先需要通过一项基本剑术测试。测试要求 Bob 尽可能快地切断 �n 根绳子。
所有绳子的端点两两不同,所以共有 2�2n 个端点。这些端点被捆在一个圆上,等距离分布。我们把这些端点按顺时针方向编号为 11 到 2�2n。
Bob 每次切割的轨迹是一条直线,可以将所有与这条直线相交的绳子切断,他想知道至少多少次可以切断所有的绳子。
输入格式
第一行一个整数 �(1≤�≤2×105)n(1≤n≤2×105),表示绳子的个数。
接下来 �n 行,每行两个整数 ��,��(1≤��,��≤2�,��≠��)ai,bi(1≤ai,bi≤2n,ai=bi),表示第 �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
说明/提示
样例一解释:
样例二解释:
样例三解释:
实际上前面几份题解虽然是错的,但找出最短距离并枚举这一段上的起点是必要的,这里要解决的是怎样用�(�/�)O(n/d)的时间(前面的问题就是用�(�)O(n)的时间)统计分割点的最小数量,这样就能保证复杂度为�(�)O(n)。
不妨设最短点对是1∼�1∼d,并记连接�i与�+1i+1( mod 2�mod2n意义下,下同)的弧编号为�i。假定我们选择从�i(弧)处断开,那么由最短性可知1∼�−11∼i−1的弧无需断开,因此仅需考虑�∼2�i∼2n的弧。这个问题的�(�)O(n)做法可以贪心(断开处必选,其余位置尽可能后移),考虑维护这个贪心。
首先断开弧2�2n(但不是完全断开,见下)
从该分割线起,所有边均指不包含弧�n的部分
引理:设边(�1,�1)(l1,r1),(�2,�2)(l2,r2)满足�1<�2<�1<�2l1<l2<r1<r2,则删除边(�1,�1)(l1,r1)对答案无影响。
这是因为边(�2,�2)(l2,r2)中有分割点时边(�1,�1)(l1,r1)中一定有分割点。
这样,我们可以过滤掉可以对答案无影响的边,这样就能按两端点同时增序枚举这些边。
定义��fi表示按照贪心策略在弧�i上选取分割点后(假设所有左端点小于等于�i的弧上均已有分割点),下一个分割点的位置。
考虑相邻的两条边(�1,�1)(l1,r1),(�2,�2)(l2,r2),那么编号在[�1,�2)[l1,l2)的弧对应的�f值为�2−1r2−1。(画个图理解一下)
我们考虑按编号从11到�−1d−1的顺序枚举第一个分割点。那么这个分割点前面是找不到完整的一条边的(否则�d就不是最短距离了),这样每次从�i跳到��fi,复杂度为�(�/�)O(n/d)(即分割点的数量上限),完结。
其实还差一点:我们错误地忽略了包含弧2�2n的边。
如果两端点都不在[1,�][1,d]的范围内,显然不用考虑。
如果都在,这是不可能的。
假定在[1,�][1,d]范围内的端点编号为�x,那么�>�x>i时不用考虑,把�=�x=i的边插入后维护�f多出的最后一段即可。
由于�f中的每一个值只会被求一次,因此复杂度是�(�)O(n)的。
#include<bits/stdc++.h>
using namespace std;
int read() {
char c=getchar();while(!isdigit(c))c=getchar();
int num=0;while(isdigit(c))num=num*10+c-'0',c=getchar();
return num;
}
int m[1000001];
int f[400001];
int main() {
int n = read();
int dis = n * 2, lp, rp;
for (int i = 1; i <= n; i++) {
int a, b;
a = read(), b = read();
if (a > b) swap(a, b);
m[a]=b, m[b]=a;
m[a+n*2]=b, m[b+n*2] = a;
if (b - a < dis)
dis = b - a, lp = a, rp = b;
if (a + n * 2 - b < dis)
dis = a + n * 2 - b, lp = b, rp = a + n * 2;
}
for (int i = 1; i <= n * 2; i++) m[i]=m[i+lp-1];
int d = rp - lp + 1;
int pt = 0, last = 1;
for (int i = d + 1; i <= n * 2; i++)
if (m[i] < i && m[i] > last) {
for (int j = last; j < m[i]; j++) f[j] = i - 1;
last = m[i];
}
int ans = n;
for (int i = 1; i < d; i++) {
if (m[i] > last) {
for (int j = last; j < m[i]; j++) f[j] = n * 2;
last = m[i];
}
int p = i, cnt = 0;
while (p) {
p = f[p];
++cnt;
}
ans = min(ans, cnt);
}
cout << (ans + 1) / 2 << endl;
}
拜拜!