洛谷 P4704 太极剑

题目描述

在学习太极之后,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;
}

拜拜! 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值