[SCOI2015] 国旗计划
题目描述
A 国正在开展一项伟大的计划 —— 国旗计划。这项计划的内容是边防战士手举国旗环绕边境线奔袭一圈。这项计划需要多名边防战士以接力的形式共同完成,为此,国土安全局已经挑选了 N N N 名优秀的边防战上作为这项计划的候选人。
A 国幅员辽阔,边境线上设有 M M M 个边防站,顺时针编号 1 1 1 至 M M M。每名边防战士常驻两个边防站,并且善于在这两个边防站之间长途奔袭,我们称这两个边防站之间的路程是这个边防战士的奔袭区间。 N N N 名边防战士都是精心挑选的,身体素质极佳,所以每名边防战士的奔袭区间都不会被其他边防战士的奔袭区间所包含。
现在,国十安全局局长希望知道,至少需要多少名边防战士,才能使得他们的奔袭区间覆盖全部的边境线,从而顺利地完成国旗计划。不仅如此,安全局局长还希望知道更详细的信息:对于每一名边防战士,在他必须参加国旗计划的前提下,至少需要多少名边防战士才能覆盖全部边境线,从而顺利地完成国旗计划。
输入格式
第一行,包含两个正整数 N , M N,M N,M,分别表示边防战士数量和边防站数量。
随后 N N N 行,每行包含两个正整数。其中第 i i i 行包含的两个正整数 C i C_i Ci、 D i D_i Di 分别表示 i i i 号边防战士常驻的两个边防站编号, C i C_i Ci 号边防站沿顺时针方向至 D i D_i Di 号边防站力他的奔袭区间。数据保证整个边境线都是可被覆盖的。
输出格式
输出数据仅 1 1 1 行,需要包含 N N N 个正整数。其中,第 j j j 个正整数表示 j j j 号边防战士必须参加的前提下至少需要多少名边防战士才能顺利地完成国旗计划。
样例 #1
样例输入 #1
4 8
2 5
4 7
6 1
7 3
样例输出 #1
3 3 4 3
提示
N ⩽ 2 × 1 0 5 , M < 1 0 9 , 1 ⩽ C i , D i ⩽ M N\leqslant 2×10^5,M<10^9,1\leqslant C_i,D_i\leqslant M N⩽2×105,M<109,1⩽Ci,Di⩽M。
倍增加贪心
使用到的技术有:
一、化圆为线
为了保持原来的首尾关系,需要把原来的圆圈复制再相接
二、贪心
考虑该区间与下一个区间的关系,先将区间按照左端点排序,因为题目说明各个区间不存在完全包含关系,所以,下一区间的左端点越大,则右端点越大,所以选择左端点小于该区间的右端点的区间,并且要求左端点尽量大
三、倍增
为了进行高效的查询,可以倍增法预计算出一些跳板,快速找到后面的区间
g
o
[
s
]
[
i
]
=
g
o
[
g
o
[
s
]
[
i
−
1
]
]
[
i
−
1
]
\ go[s][i] = go[go[s][i-1]][i-1]
go[s][i]=go[go[s][i−1]][i−1]
g
o
[
s
]
[
i
]
表示从第
s
个区间出发,走
2
i
个最优区间后到达的区间
\ go[s][i]表示从第s个区间出发,走2^i个最优区间后到达的区间
go[s][i]表示从第s个区间出发,走2i个最优区间后到达的区间
#include <bits/stdc++.h>
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 * 2];
int n2; // n2实际上是战士的数量的两倍,因为下面把环拆下来并且加倍成为一条链
int go[N][20];
void init(){
int nxt = 1;//下一个战士
for (int i = 1; i <= n2; i++){ //从第一个战士遍历到最后一个战士,用贪心法求得每个区间的下一个最优区间
while(nxt <= n2 && w[nxt].L <= w[i].R){ //从第nxt个战士开始向后查找,找到最后一个满足nxt的左区间小于i的右区间的战士
nxt++; //因为题目中说明,没有战士的区间被完全包含,所以,最后一名 L < R 一定意味着该区间右区间最大!
}
go[i][0] = nxt - 1; //第i个区间的下一个最优的区间应该是 nxt - 1,因为计算时nxt会多加一个1
}
for (int i = 1; (1 << i) <= n; i++) //倍增:(1 << i) = 1,2,4,8...共logn次 ,i是指数
for (int s = 1; s <= n2; s++){ //用倍增法找到从 s 区间跳 2^i 个最优区间到达的区间
go[s][i] = go[go[s][i - 1]][i - 1];
}
}
int res[N];
void get_ans(int x){ //从第 x 个战士出发
int len = w[x].L + m;
int cur = x, ans = 1;
for (int i = log2(N); i >= 0; i--) {//从最大的i开始找: 2^i = N
int pos = go[cur][i];
if(pos && w[pos].R < len){
ans += 1 << i; //累加跳过的区域
cur = pos;
}
}
res[w[x].id] = ans + 1;
}
int main(){
scanf(" %d %d", &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 + n + 1);
n2 = n;
for (int i = 1; i <= n; i++){//拆环加倍
n2++;
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;
}