[题] 国旗计划 #倍增 #贪心

题目

国旗计划
题目大意:

有士兵,每个人有自己的“奔袭区间”(往来区间)。
默认边防站的编号就是数轴上的对应点,用于计算距离。
输入n与m,n代表有n名士兵,m代表最大的区间。
输入n行,代表一个士兵特定的往来区间。
每位士兵的往来区间相互独立。
输出在第i个士兵参加的情况下,最少要多少人参加计划。


题解

三个技巧:

  1. 断环成链:
    if(w[i].R < w[i].L)
    w[i].R += m;
    m是环的长度;
  2. 贪心:
    选择一个区间i后,下一个区间只能从左端小于等于i的右端点的区间中选。
    但是每次都往后遍历n次的话,时间复杂度就是O(n2),超时。
  3. 倍增:
    为了高效进行查询,参考ST算法,预设好一些“跳板”,快速找到后面的区间。
    定义go[s][i],表示从第s个区间出发,走2i个最优区间后到达的区间。
    说人话就是:从s到s + 2i之间,最大的满足条件的左端点的值。
    这个操作的时间复杂度是O(nlogn)。
    肝了一个晚上,将自己遇到的几个难点说一下:
    第一个是关于狗函数,我们设从当前区间到下一合法区间为一次,那么我们要跳好多好多次……
    为了简便运算,我们利用倍增的思想进行快速跳跃,狗就是拿来这么用的。

以上所有的操作是O(nlogn) + O(nlogn)次,完全不会超时。


代码

#include<bits/stdc++.h>
using namespace std;
const int maxn = 4e5 + 10;
//go函数表示从第st区间出发,走2^i个区间到达的区间。 
//倍增要用go实现。 
int n, m, n2, go[maxn][20], res[maxn];
//W数组里面的元素结构体:地址,左右边界。 
struct w {
    int id, L, R;
}W[maxn * 2];
//sort排序规则:按左边界从小到大进行排序。 
bool operator < (w &a, w &b) {
    return a.L < b.L;
}
//神奇的预处理
void init() {
    //nxt表示下一个位置(最靠右边并能拼到下一个区间)要到哪里。 
    int nxt = 1;
    for(int i = 1; i <= n2; i ++) {
        //后面还有链,且下一个链的左端不超过当前链的右端(可以拼接)时 
        while(nxt <= n2 && W[nxt].L <= W[i].R) 
			nxt ++;
        go[i][0] = nxt - 1;
    }
    //长度:跳2^i这么远
    for(int i = 1; (1 << i) <= n; i ++)
        //起点
        for(int st = 1; st <= n2; st ++)
        	//从st区间开始跳2^i这么远到达的区间。 
        	//从st开始跳2^(i-1)再跳2^(i-1)即可完成跳2^i的目的。 
            go[st][i] = go[ go[st][i - 1] ][i - 1];
}
//从第x个战士开始的话,目标战士就一定会被包含在里面了。
void get_ans (int x) {
    //len是战士的L加一圈,用来防止跑了超过一圈。cur是当前旗帜的位置。ans是答案。 
    int len = W[x].L + m, cur = x, ans = 1;
    //i:跳2^i这么远,区间越来越小。 
    for(int i = log2(maxn); i >= 0; i --) {
        //大步大步地跳,跳到的位置没有超过len就是合法的跳
        //nxt就是下一个接手旗帜的老哥。 
        int nxt = go[cur][i];
        //首先往右跨那么多步的区间要存在(不为0), 
        //其次是这个区间的R小于len, 
        //最后一点,先不要越过起点的左端点, 
        //因为我们要最少的人,越往后跨过的区间越少(一个人对应一个区间)。 
        if(nxt && W[nxt].R < len) {
            //这中间跳过了2^i个人,要加上。
            ans += 1 << i;
            //旗帜交到第nxt个老哥手上。 
            cur = nxt;
        }
    }
	//因为W[nxt].R < len,
	//所以旗帜位置不会超过一圈,此时还没有走完一圈,旗帜还在倒数第二个老哥手上的。
    //所以最后答案加1。 
    res[W[x].id] = ans + 1;
}
int main() {
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i ++) {
    	//输入士兵所在区间。 
        scanf("%d %d", &W[i].L, &W[i].R);
        W[i].id = i;
        //将圆展开成链。 
        //要是R比L小,那就将R加一圈,保证区间是往右边延长的。 
        if(W[i].R < W[i].L) 
			W[i].R += m;
    }
    //按照L进行排序,按照R进行排序也可以。 
    sort(W + 1, W + n + 1);
    n2 = n;
    //拆成链,所有的链都往后延长,再加入一次队列。 
    //因为是圆的,所以我们确保:
	//	无论从谁开始,都能够完整地按顺序访问所有人的往来区间。 
    for(int i = 1; i <= n; i ++) {
        n2 ++;
        //对应原链 
        W[n2] = W[i];
        W[n2].L = W[i].L + m;
        W[n2].R = W[i].R + m;
    }
    //预处理。 
    init();
    //逐个计算每个战士。
    for(int i = 1; i <= n; i ++) 
		get_ans(i);
	//逐个输出答案。 
    for(int i = 1; i <= n; i ++) 
		printf("%d ", res[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值