[luoguT30208]太极剑

Posted xjr_01

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[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;
}

以上是关于[luoguT30208]太极剑的主要内容,如果未能解决你的问题,请参考以下文章

luoguT21778 过年

计数难题3:LuoguT46780 妹子序列

旋转太极八卦

太极链_区块链将成为保险行业的新环境

CSS3 实现太极图案

纯css3制作旋转太极图案