洛谷 P4155 国旗计划(倍增法+贪心)

倍增法

     倍增就是“成倍增长”,可以简单地乘以2,也可以利用二进制本身的倍增特性,以1,2,4,8...为跳板,也就是以2^i为跳板。

国旗计划

题目:

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

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

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

输入格式

第一行,包含两个正整数 𝑁,𝑀,分别表示边防战士数量和边防站数量。

随后 𝑁行,每行包含两个正整数。其中第𝑖行包含的两个正整数𝐶𝑖​、𝐷𝑖分别表示𝑖号边防战士常驻的两个边防站编号,𝐶𝑖号边防站沿顺时针方向至 𝐷𝑖号边防站力他的奔袭区间。数据保证整个边境线都是可被覆盖的。

输出格式

输出数据仅1行,需要包含 𝑁个正整数。其中,第 𝑗 个正整数表示 𝑗号边防战士必须参加的前提下至少需要多少名边防战士才能顺利地完成国旗计划。

说明/提示

𝑁⩽2*10^5,𝑀<10^9,1⩽𝐶𝑖,𝐷𝑖⩽𝑀。

思路:

首先,题目中给的是一个环,但为了方便处理可以将它变成线,采取的方式是如果右边𝐷𝑖小于左边𝐶𝑖​,则将右边𝐷𝑖数值加上𝑀,拆开后为了保持之前的首尾关系,就需要把原来的复制再相接,先将这些区间都按左端点进行排序,当选择一个区间𝑖后,下一个区间只能从左端点小于或等于𝑖的右端点的那些区间中选择,选择右端点最大的那一个,这里是贪心算法的思想,但光是这样,时间复杂度为O(n^2),想要效率更高,可以采用倍增法,找出一些“跳板”,go[s][i]示从第s个区间出发,走2^i个最优区间后到达的区间,这个方法运用了二进制的特征,将数字转化成二进制,跳的时候先是大数再是小数,比如19,就是10011,那就是跳了16+2+1步,此时时间复杂度为O(n\log_{2}n)。

代码:

#include<bits/stdc++.h>
using namespace std;

int n, m;
int n2;
int go[400005][20];
int res[400005];
 
struct person{   
    int id;//id:战士的编号
	int left, right;//战士的左右区间
	//按左边排序 
    bool operator < (const person b) const{
	     return left < b.left;
	}
}w[400005];

void fun1() { 
    int next = 1;
    for(int i = 1;i <= n2; i++) { 
    //每个区间的下一个是右端点最大的那个区间
        while(next <= n2 && w[next].left <= w[i].right) {
        	next++;
		}
        go[i][0] = next - 1; //区间i的下一个最优区间
    }
    //倍增
    for(int i = 1; (1 << i) <= n; i++) {//1 << i即将1转换为二进制数后,左移i位
		for(int s = 1; s <= n2; s++) {
			go[s][i] = go[go[s][i - 1]][i - 1];//go[s][i]表示从第s个区间出发,走2^i个最优区间后到达的区间 
		}      
	}    
}

void fun2(int x) { 
    int len = w[x].left + m;//从第x个战士出发,移动一圈 
	int cur = x, ans = 1;
    for(int i = log2(n); i >= 0; i--) { 
        int pos = go[cur][i];
        if(pos && w[pos].right < len) {
            ans += 1 << i; //累加跳过的区
            cur = pos; 
        }
    }
    res[w[x].id] = ans + 1;
}

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++) {
        w[i].id = i; 
        cin >> w[i].left >> w[i].right;
        if(w[i].right < w[i].left) 
            w[i].right += m;
    }
    sort(w+1, w+n+1); 
    n2 = n;//n2就是复制后再相接 
    for(int i = 1; i <= n; i++) { 
        n2++; 
		w[n2].id = w[i].id; 
		w[n2].left = w[i].left + m;
		w[n2].right = w[i].right + m;
    }
    fun1();
    for(int i = 1;i <= n; i++) {
    	fun2(i); 
	}
    for(int i = 1; i <= n; i++) {
    	cout << res[i] << " ";
	}
    return 0;
}

  • 17
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值