[Luogu P4155] [BZOJ 4444] [SCOI2015]国旗计划

洛谷传送门
BZOJ传送门

题目描述

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 、 D i C_i、D_i CiDi 分别表示 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:
4 8
2 5
4 7
6 1
7 3
输出样例#1:
3 3 4 3

说明

N ⩽ 2 × 1 0 5 , M &lt; 1 0 9 , 1 ⩽ C i , D i ⩽ M N\leqslant 2×10^5,M&lt;10^9,1\leqslant C_i,D_i\leqslant M N2×105,M<109,1Ci,DiM

解题分析

首先还是老套路, 破环为链。因为区间不互相包含, 所以直接贪心取区间内最靠右的那个跳, 这样我们可以得到一个 O ( N 2 ) O(N^2) O(N2)的算法。

既然都可以贪心跳了, 我们为什么不倍增一下? 预处理出跳 2 k 2^k 2k能到哪个位置即可。

注意因为跨越 n n n的这个区间不会出现第二次, 所以破环为链后可能看似跳不到终点, 我们就将错就错, 直接跳到之前一个停下来, 最后答案 + 1 +1 +1即可。

代码如下:

#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <cstdlib>
#include <cmath>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define MX 400500
#define ll long long
template <class T>
IN void in(T &x)
{
	x = 0; R char c = gc;
	for (; !isdigit(c); c = gc);
	for (;  isdigit(c); c = gc)
	x = (x << 1) + (x << 3) + c - 48;
}
struct INFO {int lef, rig, id;} dat[MX];
int dot, num, cnt;
int to[MX][20], nex[MX], pw[20], ans[MX];
IN bool operator < (const INFO &x, const INFO &y)
{return x.lef == y.lef ? x.rig < y.rig : x.lef < y.lef;}
IN int find(R int x)
{
	int lef = 1, rig = cnt + 1, ans, mid;
	W (lef <= rig) mid = lef + rig >> 1, dat[mid].lef <= x ? (lef = mid + 1, ans = mid) : (rig = mid - 1);
	return ans;
}
IN int query(R int now)
{
	int ans = 0, tar = dat[now].lef + num;
	for (R int i = 19; ~i; --i)
	if(dat[to[now][i]].rig < tar) now = nex[to[now][i]], ans += pw[i];
	return ans;
}
int main(void)
{
	int a, b;
	in(dot), in(num); pw[0] = 1;
	for (R int i = 1; i <= 19; ++i) pw[i] = pw[i - 1] << 1;
	for (R int i = 1; i <= dot; ++i)
	{
		in(a), in(b);
		if(b > a) dat[++cnt] = {a, b, i}, dat[++cnt] = {a + num, b + num, i};
		else dat[++cnt] = {a, b + num, i};
	}
	std::sort(dat + 1, dat + 1 + cnt); dat[cnt + 1].rig = 2e9 + 5;
	for (R int i = 0; i < 20; ++i) for (R int j = cnt + 1; j; --j) to[j][i] = cnt + 1;
	for (R int i = 1; i <= cnt; ++i) to[i][0] = i, nex[i] = find(dat[i].rig); nex[cnt + 1] = cnt + 1;
	for (R int i = 1; i < 20; ++i)
	for (R int j = 1; j <= cnt; ++j)
	to[j][i] = to[nex[to[j][i - 1]]][i - 1];
	for (R int i = 1; i <= cnt; ++i) if(!ans[dat[i].id]) ans[dat[i].id] = query(i) + 1;
	for (R int i = 1; i <= dot; ++i) printf("%d ", ans[i]);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值