[SCOI2015] 国旗计划

发布时间 2023-06-29 19:47:52作者: Morning_Glory

也许更好的阅读体验

\(\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;
}

如有哪里讲得不是很明白或是有错误,欢迎指正
如您喜欢的话不妨点个赞收藏一下吧