[SCOI2015] 国旗计划
\(\mathcal{Description}\)
给一个长度为\(m +1\)的环,上面有\(n\)条线段,问在第\(i\)条线段必须被选中的条件下覆盖整个环最少需要多少线段,不存在线段覆盖线段的情况,注意是线段,\([1, 2]\)和\([3,4]\)之间少了\([2,3]\)这一段
\(n \le 2 * 10^5,\ m \lt 10^9\)
\(\mathcal{Solution}\)
用倍增,预处理出一个类似\(st\)表的表出来
环的问题先倍长数组,然后将线段也双倍
将线段按照左端点大小排序,由于不存在线段覆盖情况,因此可以肯定没有两条线段有端点相等的情况,并且左端点更大的线段右端点也更大
设\(f[i][j]\)表示排序后第\(i\)条线段开始往后挑选\(2^j\)条线段并且使得覆盖的长度最长,最后一条线段的序号
考虑状态转移时并不是直接等于\(f[f[i][j - 1]][j-1]\),因为最后一条线段是已经被选了的,因此应该从下一条线段开始
这里的下一条线段并不是序号+1,而应该是左端点在最后一条线段右端点范围内的尽可能大的那一条
这是很明显的贪心,如果有多条线段可以和当前覆盖范围拼起来,当然选能够覆盖的更多的线段
我们可以预处理出\(nxt[i]\),表示第\(i\)条线段的下一条线段是哪一个,因为从左到右\(nxt[i]\)只会越来越大,因此\(O(n)\)就可以处理出来
转移方程就变成了\(f[i][j] = f[nxt[f[i][j - 1]]][j - 1]\)
查询时,我们用类似求\(LCA\)那样的方法不断接近覆盖整个环但始终不覆盖,最后就能得出还差一条线段就可以覆盖环的结果
\(\mathcal{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;
}
如有哪里讲得不是很明白或是有错误,欢迎指正
如您喜欢的话不妨点个赞收藏一下吧