luogu P4155 [SCOI2015] 国旗计划

[SCOI2015] 国旗计划

题目描述

A 国正在开展一项伟大的计划 —— 国旗计划。这项计划的内容是边防战士手举国旗环绕边境线奔袭一圈。这项计划需要多名边防战士以接力的形式共同完成,为此,国土安全局已经挑选了 $N$ 名优秀的边防战士作为这项计划的候选人。


A 国幅员辽阔,边境线上设有 $M$ 个边防站,顺时针编号 $1$ 至 $M$。每名边防战士常驻两个边防站,并且善于在这两个边防站之间长途奔袭,我们称这两个边防站之间的路程是这个边防战士的奔袭区间。$N$ 名边防战士都是精心挑选的,身体素质极佳,所以每名边防战士的奔袭区间都不会被其他边防战士的奔袭区间所包含。


现在,国土安全局局长希望知道,至少需要多少名边防战士,才能使得他们的奔袭区间覆盖全部的边境线,从而顺利地完成国旗计划。不仅如此,安全局局长还希望知道更详细的信息:对于每一名边防战士,在他必须参加国旗计划的前提下,至少需要多少名边防战士才能覆盖全部边境线,从而顺利地完成国旗计划。

具体的,题目给定n个不相互包含区间环和覆盖范围m,求每个区间在选定的条件下最少需要几个区间满足全覆盖。对给定的区间环转换成链,创建a数组,a[0],a[1]分别表示每个区间的左右端点,a[2]表示每个区间的id,便于最后按顺序输出答案。为了使每个区间尽可能覆盖最大的范围,可以贪心的对区间进行排序,这里对每个区间的左端点进行了排序,接下来按照题意如果一一对每个区间进行枚举达到n^2的复杂度会超时,可以考虑倍增法对于每个区间记录倍增若干次后能到达的最远的区间(保证范围不超过对当前区间的相对值m),g[j][i]代表j区间倍增2^i次后达到的最远区间,先利用双指针维护每个点能到达的最远区间后进行递推,递推方程为g[j][i]=g[g[j][i-1]][i-1]。search函数里len为对当前区间来说的覆盖范围,即当前左端点加上题目给定的范围m,也就是对覆盖范围进行对于当前区间的偏移,cur记录当前区间,让i从大到小枚举,若a[g[cur][i]]<len,也就是当前点能到达的最右区间如果不超过覆盖范围,则倍增跳到g[cur][i],并记录ans,在这里ans的初始值为1,是因为判断条件中a[pos][1] < len,使得最后总无法覆盖最后一部分区间,这里直接增加1覆盖掉这个区间,返回答案时同时加上初始区间。运用倍增算法,使时间复杂度从暴力的n^2降到了nlogn。

#include <bits/stdc++.h>
using namespace std;
const int N = 4e5 + 500;
#define endl '\n'
#define ll long long
const int MOD = 1e9 + 7;
int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    ll n, m;
    cin >> n >> m;
    vector<array<ll, 3>> a(n * 2);
    vector<vector<ll>> g(n * 2, vector<ll>(20, 0));
    vector<ll> res(n);
    for (int i = 0; i < n; i++)
    {
        cin >> a[i][0] >> a[i][1];
        if (a[i][0] > a[i][1])
            a[i][1] += m;
        a[i][2] = i;
    }
    sort(a.begin(), a.begin() + n);
    for (int i = 0; i < n; i++)
    {
        a[i + n] = a[i];
        a[i + n][1] += m;
        a[i + n][0] += m;
    }
    for (int i = 0, p = 0; i < n * 2; i++)
    {
        while (p < n * 2 && a[p][0] <= a[i][1])
            p++;
        g[i][0] = p - 1;
    }
    for (int i = 1; (1 << i) < n; i++)
    {
        for (int j = 0; j < n * 2; j++)
        {
            g[j][i] = g[g[j][i - 1]][i - 1];
        }
    }
    auto search = [&](ll x)
    {
        ll len = a[x][0] + m;
        ll cur = x;
        ll ans = 1;
        for (int i = 19; i >= 0; i--)
        {
            ll pos = g[cur][i];
            if (pos && a[pos][1] < len)
            {
                cur = pos;
                ans += (1 << i);
            }
        }
        res[a[x][2]] = ans+1;
    };
    for (int i = 0; i < n; i++)
    {
        search(i);
    }
    for (int i = 0; i < n; i++)
    {
        cout << res[i] << " ";
    }

    return 0;
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值