Codeforces 1285E Delete a Segment

Description

描述

给一个数轴上的 $n$ 条线段,第 $i$ 条覆盖了 $[l_i, r_i]$(如果 $l_i = r_i$ 的话就是一个点)。一个线段集的 并集 是一个与原线段集所覆盖点一样的线段集,例如 $n = 5$,$5$ 条线段分别为 $[1,2]$,$[2,3]$,$[4,5]$,$[4,6]$,$[6,6]$,那么它们的 并集 就是 $[1,3]$ 和 $[4,6]$ 两条线段。现在,你要求出 正好删去一条线段后并集 最多包括多少条线段。

如果两条线段重合,你依然只能删去一条。

输入

第一行是一个正整数 $t(1 \le t \le 10^4)$,表示数据组数。

每组数据的第一行是一个正整数 $n(2 \le n \le 2 \cdot 10^5, \sum n \le 2 \cdot 10^5)$,接下来 $n$ 行每行两个整数 $l_i, r_i(-10^9 \le l_i, r_i \le 10^9)$,含义如描述所示。

输出

对于每组数据输出一个数,表示 正好删去一条线段后并集 最多包括多少条线段。

样例

输入

3
4
1 4
2 3
3 6
5 7
3
5 5
5 5
5 5
6
3 3
1 1
5 5
1 5
2 2
4 4

输出

2
1
5

解释

样例解释:

  • 删去 $[1,4]$,剩下 $[2,3]$,$[3,6]$,$[5,7]$,并集 大小为 $1$;
  • 删去 $[2, 3]$,剩下 $[1, 4]$,$[3,6]$,$[5,7]$,并集 大小为 $1$;
  • 删去 $[3, 6]$,剩下 $[1, 4]$,$[2, 3]$,$[5,7]$,并集 大小为 $2$;
  • 删去 $[5, 7]$,剩下 $[1, 4]$,$[2, 3]$,$[3, 6]$,并集 大小为 $1$。

故输出为 $2$。

Solution

看到负数,首先考虑 离散化,然后就很自然地想到用 $d_i$ 来表示坐标(离散化后)为 $i$ 的点被几条线段覆盖,然后姑且不说删去一条线段,就是原线段集的 并集 怎么求呢?

画个图看看(图中为了防止线段重叠将线段进行了竖直平移,一条线段的实际覆盖区域即为其竖直投影):

简单!连续非零段的个数就是 并集 大小!

真的是这样吗?

似乎……不太对?

于是就有了一种神仙的方法:既然我们的标号都在点上,第二张图不能很好处理,那么,我们给边也来个 $d$!

怎么实现呢?只要把所有的 $l_i, r_i$ 都 $\times 2$ 即可!

于是我们就能很好地处理了:

那么 $\mathcal O(n^2)$ 的做法就呼之欲出了:删去线段 $i$ 时,就把 $[l_i, r_i]$ 的 $d$ 都减去 $1$,然后重新统计一遍求最大值!

显然是过不了的,还需要优化。

把 $[l_i, r_i]$ 的 $d$ 都减去 $1$ 后,有能力成为新的空段的条件,显然是 $d = 1$,我们不妨大胆的假设一下,$[l_i, r_i]$ 中 $d = 1$ 的连续段数(显然这里的 $d$ 不会为 $0$,至少有线段 $i$ 覆盖呢),就是删去线段 $i$ 后 新增的段数

试一下?

好像是对的?

那要是这样呢?

可是显然答案是增加 $1$,而不是增加 $2$ 呀?

再举个最简单的例子:

这时,显然答案是减去 $1$ 了,而不是离谱地增加 $1$。(虽然 $n \ge 2$,但这里姑且认为那个 $l$ 和 $r$ 非常大)

发现了什么?当新增段和 $l_i$ 相通时,答案要 $-1$,和 $r_i$ 相通时也要 $-1$,实现上只要判断一下 $d_{l_i}$ 和 $d_{r_i}$ 是不是等于 $1$ 就好了。

然后怎么求新增段呢?很简单,把每一个连续 $1$ 段的最左边的 $d' + 1$,最右边的 $d' + 1$,我们就可以用 $\left \lfloor \dfrac{\sum_{i = l}^{r} d'_i}{2} \right \rfloor $ 来计算段数了(想一想,为什么?),如果加上前缀和优化,那么去掉 $\mathcal O(n \log n)$ 的离散化,代码就是 $\mathcal O(n)$ 的了。

下面是一个直观的演示:

代码贴出,仅供参考。(代码中用  d_  来表示 $d'$)

#include <bits/stdc++.h>
#define REP(i, x, y) for(register int i = x; i <= y; i++)
using namespace std;
typedef long long LL;
const int N = 2e5 + 5;
struct segment { LL l, r; } s[N];
int n;
LL ans0, ans1, tmp[N << 1], d_[N << 2], d[N << 2];
int main()
{
	ios::sync_with_stdio(false);
	int T;
	cin >> T;
	while(T--)
	{
		ans0 = 0; ans1 = INT_MIN;
		cin >> n;
		REP(i, 1, n)
		{
			cin >> s[i].l >> s[i].r;
			tmp[(i << 1) - 1] = s[i].l;
			tmp[i << 1] = s[i].r;
		}
		sort(tmp + 1, tmp + (n << 1) + 1);
		int tot = unique(tmp + 1, tmp + (n << 1) + 1) - tmp - 1;
		REP(i, 1, n)
		{
			s[i].l = lower_bound(tmp + 1, tmp + tot + 1, s[i].l) - tmp;
			s[i].r = lower_bound(tmp + 1, tmp + tot + 1, s[i].r) - tmp;
			s[i].l <<= 1LL; s[i].r <<= 1LL; 
			d[s[i].l]++; d[s[i].r + 1]--; // 差分算原始数组 
		}
		tot <<= 1LL;
		REP(i, 1, tot + 5) d[i] += d[i - 1];
		REP(i, 0, tot + 5) ans0 += d[i] && !d[i + 1];
		REP(i, 0, tot + 4) if(d[i] == 1 && d[i + 1] != 1) d_[i]++;
		REP(i, 1, tot + 5) if(d[i] == 1 && d[i - 1] != 1) d_[i]++;
		REP(i, 1, tot + 5) d_[i] += d_[i - 1]; // d_[] 上前缀和 
		REP(i, 1, n)
		{
			LL t = (d_[s[i].r] - d_[s[i].l - 1]) / 2; 
			t -= (d[s[i].r] == 1) + (d[s[i].l] == 1);
			ans1 = max(ans1, t);
		}
		cout << ans0 + ans1 << endl; // 初始答案加上新增答案 
		REP(i, 0, tot + 10) d[i] = d_[i] = 0; // 用多少清多少,避免 memset 浪费时间 
	}
	return 0;
}
posted @ 2020-01-13 19:24  syksykCCC  阅读(454)  评论(0编辑  收藏  举报