CF1462F 区间的计算
1 CF1462F 区间的计算
2 题目描述
\(leb\) 有 \(n\) 段区间,每段区间都是由两个元素 \(l_i\) 和 \(r_i\) 所确定的。如果这 \(n\) 段区间内,存在一个区间跨越了所有的区间(即该区间与任意一个区间都有重叠部分),那么 \(leb\) 就会很开心,\(lht\) 就可以乘机蹭一顿饭。\(lht\) 悄悄找到了给出这 \(n\) 段区间的 \(zzt\),想让他动些手脚。可是 \(zzt\) 不肯放过这个赚钱的好机会:“你可以删掉一些区间,但每删一段区间就得交 \(1\) 块钱。” \(lht\) 并没有很多钱,现在他想知道,让 \(leb\) 开心的最小花费。
3 解题思路
删除一些区间,这种问题一般很难让人产生什么思路。我们可以将问题稍微转化一下:我们发现,如果某个区间在最优方案中应该被删除,意思就是这些区间并没有被最终的那一个区间所跨越。换言之,就是最终区间没有跨越的区间数。因为区间总数一定,所以求没有跨越的区间数就可以转化为求跨越区间最多的区间跨越的区间数 \(ans\)(输出时要输出 \(n - ans\))。
我们接下来分析一下跨越的性质。
我们假设两个区间,第一个区间左端点是 \(l_i\),右端点是 \(r_i\);第二个区间左端点是 \(l_j\),右端点是 \(r_j\)。如果 \(l_j \le l_i \le r_j\),那么这两个区间肯定互相跨越。如果 \(l_i < l_j\) 并且 \(l_j \in (l_i, r_i]\),那么这两个区间也肯定互相跨越。
分析完性质后,我们发现:这两种情况的条件中都包含有 \(l_i\) 与 \(l_j\) 这两个元素的大小关系,且对于第一种情况,\(l_j \le l_i\),第二种则是 \(l_j > l_i\)。这时,如果我们将所有区间按照左端点递增排序,那么 \(\forall i < j, l_i \le l_j(i, j \in [1,n])\)。所有坐标小于当前区间的区间一定满足第一种条件中的 \(l_j \le l_i\),所有坐标大于当前区间的区间一定满足 \(l_j \ge l_i\)。这时我们发现:所有坐标大于当前区间的区间满足的并不是第二种情况所需要的条件,有些人可能会问:那直接把条件改成 \(l_i \le l_j\) 并且 \(l_j \in [l_i, r_i]\) 不就好了?这里有一个问题需要我们注意:在 \(l_j = l_i\) 时,两种情况都满足,那么答案理论上就会重复计算。但是,往往事情就是这么巧,由于坐标独特的性质,这样做不会重复计算:对于当前区间的左端点,可能存在部分区间左端点与其一样并且坐标小于当前区间的坐标,也可能存在部分区间左端点与其一样并且坐标大于当前区间的坐标。这个时候坐标小于当前区间的区间的左端点只涵盖了一部分左端点相同的情况,并不是全部情况。这剩下的空缺正好由坐标大于当前区间的左端点与当前区间的左端点相同的区间所补充上。这样一来,保证了所有的计算不重不漏地把所有的情况都囊括了进去。
具体实现中,我们可以用权值线段树来维护在所有坐标小于当前区间(第 \(i\) 个区间)的区间中,有多少个区间中的右端点 \(\in [l_i, +∞]\)。这个结果便是满足第一种情况的区间数。对于第二次情况,要维护的就是在所有坐标大于当前区间的区间中,有多少个区间中的左端点 \(\in [l_i,r_i]\)(关于为什么这么做是合法的,上文已经给出)。这个结果便是满足第二种情况的区间数。从所有区间的这两次统计的和中找出最大的值,便是跨越区间最多的区间跨越的区间数,\(ans\)。
注意:由于这题 \(T \le 2*10^5\),所以每组数据中负责统计的数组每次只用清前 \(n\) 个数为 \(0\)。如果直接 \(memset\) 会导致 \(TLE\)。
4 代码实现
代码(空格警告):
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 2e5+10;
int T, n, m, cnt, maxn;
int b[N*2], a[N], c[N*2];
struct sec
{
int l, r;
}s[N];
struct node
{
int l, r, sum;
}t[N*8];
bool cmp(sec x, sec y)
{
return x.l < y.l;
}
void discrete1()
{
sort(c+1, c+cnt+1);
for (int i = 1; i <= cnt; i++)
{
if (i == 1 || c[i] != c[i-1]) b[++m] = c[i];
}
}
int get(int x)
{
return lower_bound(b + 1, b + m + 1, x) - b;
}
void build(int p, int l, int r)
{
t[p].l = l;
t[p].r = r;
if (l == r)
{
t[p].sum = 0;
return ;
}
int mid = (l+r)/2;
build(p*2, l, mid);
build(p*2+1, mid+1, r);
t[p].sum = 0;
}
void modify(int p, int pos, int d)
{
if (t[p].l == t[p].r)
{
t[p].sum += d;
return ;
}
int mid = (t[p].l + t[p].r) / 2;
if (mid >= pos) modify(p*2, pos, d);
else modify(p*2+1, pos, d);
t[p].sum = t[p*2].sum + t[p*2+1].sum;
}
int query(int p, int l, int r)
{
if (t[p].l >= l && t[p].r <= r) return t[p].sum;
int mid = (t[p].l + t[p].r) / 2;
int ans = 0;
if (mid >= l) ans += query(p*2, l, r);
if (mid < r) ans += query(p*2+1, l, r);
return ans;
}
int main()
{
scanf("%d", &T);
while (T--)
{
maxn = 0;
m = 0;
cnt = 0;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d %d", &s[i].l, &s[i].r);
c[++cnt] = s[i].l, c[++cnt] = s[i].r;
a[i] = 0;
}
sort(s+1, s+n+1, cmp);
discrete1();
build(1, 1, m);
for (int i = 1; i <= n; i++)
{
modify(1, get(s[i].r), 1);
a[i] += query(1, get(s[i].l), m);
}
build(1, 1, m);
for (int i = n; i >= 1; i--)
{
a[i] += query(1, get(s[i].l), get(s[i].r));
modify(1, get(s[i].l), 1);
}
for (int i = 1; i <= n; i++) maxn = max(maxn, a[i]);
printf("%d\n", n-maxn);
}
return 0;
}