洛谷 国旗计划

题目描述

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

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

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

输入格式

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

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

输出格式

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

输入输出样例

输入 #1

4 8
2 5
4 7
6 1
7 3

输出 #1

3 3 4 3

说明/提示

解题思路

首先先把环变链,这要好算一些。因为士兵相互之间不包含,所以我们先对他进行排序。

然后扩环加倍成链(要判断他们是否到达终点)。

接下来怎么做呢?

贪心思想:我们找到一个士兵i,因为不包含,所以我们只要找左端点在i范围内的;又因为我们已经对他们排序,所以只要找的左端点最大的那个就行了。直到走完一圈,这样我们就找到了最少需要几个士兵。

题目问题是每一个士兵,那么我们需要n次查询,n次遍历,这样时间复杂度变成了O(n^2),不用想,肯定超时了。

怎么办?这时候倍增法来了,我们用一个数组go[N][20](20因为2的20次方已经超过了n的范围了)作为跳板记录,每次扩大2倍。具体自己找一下倍增法资料,讲的感觉不是很清楚。、

核心公式:go[s][i]=go[go[s][i-1]][i-1]

我们先把每个士兵下一步跳哪用g[i][0]记录。就用go[1][1]=go[go[1][0]][0]解释吧

go[1][0]也就是下一步最优给哪个士兵。

我们假设k是他的最优,那么代表go[1][1]=go[k][0];以此类推这样为此就是2^(i+1)。

希望能理解,本人能力有限,望大佬指正。

这样优化后时间复杂度就变成了O(nlogn)

AC代码

#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int N=4e5+1;
int n,m;
struct warrior{
	int id,l,r;
	bool operator <(const warrior b)const{
		return l<b.l;
	}
}w[N];
int n2;
int go[N][20];

void init(){
	int nxt=2;
	for(int i=1;i<=n2;i++){
		while(nxt<=n2&&w[i].r>=w[nxt].l) nxt++;//w[nxt].l<=w[i].r也可以,注意的是两者都要加等于 
		go[i][0]=nxt-1;
	}
	for(int i=1;(1<<i)<=n;i++){
		for(int s=1;s<=n2;s++){
			go[s][i]=go[go[s][i-1]][i-1];
		}
	}
}

int res[N];
void getans(int x){
	int len=w[x].l+m,cur=x,ans=0;
	for(int i=log2(n);i>=0;i--){
		int pos=go[cur][i];
		if(pos&&w[pos].r<len){        //如果不成立,代表走多了区间,不执行直接缩小就行
			ans+=1<<i;
			cur=pos; 
		//	printf("%d\n",ans);
		}
	}
	res[w[x].id]=ans+2;//ans中没有算第一个人,也没有算最后一个人,所以要加2
//	printf("%d %d\n",ans,res[w[x].id]);
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		w[i].id=i;
		scanf("%d %d",&w[i].l,&w[i].r);
		if(w[i].r<w[i].l) w[i].r+=m;
	}
	sort(w+1,w+1+n);
	n2=n;
	for(int i=1;i<=n;i++){
		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++) getans(i);
	for(int i=1;i<=n;i++) printf("%d ",res[i]); 
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

j2189259313

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值