D
e
s
c
r
i
p
t
i
o
n
\mathcal{Description}
Description
给一个长度为
m
+
1
m +1
m+1的环,上面有
n
n
n条线段,问在第
i
i
i条线段必须被选中的条件下覆盖整个环最少需要多少线段,不存在线段覆盖线段的情况,注意是线段,
[
1
,
2
]
[1, 2]
[1,2]和
[
3
,
4
]
[3,4]
[3,4]之间少了
[
2
,
3
]
[2,3]
[2,3]这一段
n
≤
2
∗
1
0
5
,
m
<
1
0
9
n \le 2 * 10^5,\ m \lt 10^9
n≤2∗105, m<109
S
o
l
u
t
i
o
n
\mathcal{Solution}
Solution
用倍增,预处理出一个类似
s
t
st
st表的表出来
环的问题先倍长数组,然后将线段也双倍
将线段按照左端点大小排序,由于不存在线段覆盖情况,因此可以肯定没有两条线段有端点相等的情况,并且左端点更大的线段右端点也更大
设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示排序后第
i
i
i条线段开始往后挑选
2
j
2^j
2j条线段并且使得覆盖的长度最长,最后一条线段的序号
考虑状态转移时并不是直接等于
f
[
f
[
i
]
[
j
−
1
]
]
[
j
−
1
]
f[f[i][j - 1]][j-1]
f[f[i][j−1]][j−1],因为最后一条线段是已经被选了的,因此应该从下一条线段开始
这里的下一条线段并不是序号+1,而应该是左端点在最后一条线段右端点范围内的尽可能大的那一条
这是很明显的贪心,如果有多条线段可以和当前覆盖范围拼起来,当然选能够覆盖的更多的线段
我们可以预处理出
n
x
t
[
i
]
nxt[i]
nxt[i],表示第
i
i
i条线段的下一条线段是哪一个,因为从左到右
n
x
t
[
i
]
nxt[i]
nxt[i]只会越来越大,因此
O
(
n
)
O(n)
O(n)就可以处理出来
转移方程就变成了
f
[
i
]
[
j
]
=
f
[
n
x
t
[
f
[
i
]
[
j
−
1
]
]
]
[
j
−
1
]
f[i][j] = f[nxt[f[i][j - 1]]][j - 1]
f[i][j]=f[nxt[f[i][j−1]]][j−1]
查询时,我们用类似求
L
C
A
LCA
LCA那样的方法不断接近覆盖整个环但始终不覆盖,最后就能得出还差一条线段就可以覆盖环的结果
C o d e \mathcal{Code} Code
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 400005;
const int lim = 21;
struct seg{
int l, r, id;
}s[maxn];
int n, m;
int lg[maxn], mi[maxn], ans[maxn], nxt[maxn];
int f[maxn][lim];
bool cmp (seg x, seg y)
{
return x.l < y.l;
}
void deal ()
{
mi[0] = 1;
for (int i = 1; i < lim; ++i) mi[i] = mi[i - 1] << 1;
for (int i = 1; i <= n; ++i) f[i][0] = i;
for (int p = 2, i = 1; i <= n; ++i) {
while (p < n && s[p + 1].l <= s[i].r) ++p;
nxt[i] = p;
}
for (int i = 1; i <= lg[n]; ++i)
for (int j = 1; j <= n; ++j) {
if (n - j + 1 >= mi[i]) f[j][i] = f[nxt[f[j][i - 1]]][i - 1];//只需求前面n条线段的答案,因此第2n条线段是不可能被用上的,若f[j][i]被赋值为0就说明不需要用这么多线段就可以覆盖环了
else break;
}
}
int query (int x)
{
int res = 0, lt = s[x].l;
for (int i = lg[n - x + 1]; ~i; --i)
if (f[x][i] && s[f[x][i]].r - lt + 1 <= m)//f[x][i]为0说明不需要跳这么远
res += mi[i], x = nxt[f[x][i]];
return res + 1;
}
int main ()
{
scanf("%d%d", &n, &m);
lg[0] = -1;
for (int i = 1; i <= n; ++i) {
scanf("%d%d", &s[i].l, &s[i].r);
if (s[i].r < s[i].l) s[i].r += m;
}
for (int i = 1; i <= n; ++i) {
s[i + n].l = s[i].l + m;
s[i + n].r = s[i].r + m;
}
n <<= 1;
for (int i = 1; i <= n; ++i) s[i].id = i, lg[i] = lg[i >> 1] + 1;
sort(s + 1, s + n + 1, cmp);
deal();
for (int i = 1; i <= n / 2; ++i)
ans[s[i].id] = query(i);
n >>= 1;
for (int i = 1; i <= n; ++i) printf("%d ", ans[i]);
return 0;
}
如有哪里讲得不是很明白或是有错误,欢迎指正
如您喜欢的话不妨点个赞收藏一下吧