[SCOI2015]国旗计划

遇到环有两种处理方式,一种断环为链,一种两次 dp,本题中看起来两种都不好,其实需要断环为链。
做出选择需要我们先转化题意。“用最少的对象做完某件事”常常转化为“用一定数量的对象最多能做多少活”。本题转为最大化一定数量的人能走的距离。这跟用 \(k\) 步(步长可选)最多能走多远是出奇地类似的,我们知道这是倍增的基本问题。大概可以想到用倍增来维护固定起点从第 \(i\) 人出发,使用 \(2^j\) 个人,最后一个人最远是谁(\(f[i][j]\))。所谓最远,是说 \(l_i\) 最大(因为此题保证区间不包含,所以 \(l_i\) 越大,\(r_i\) 越大,可以贪心)。不难发现可以用倍增维护,\(f[i][j]=f[nxt(f[i][j-1])][j-1]\)\(nxt(p)\) 表示第 \(p\) 人的接力棒最远给谁。
最后对每个人 \(i\),从大到小枚举 \(j\),去贪心地跳,设当前位置 \(x\),若 \(f[x][j]\) 已经到了/超过终点,就更新答案,如果还没到,就跳,不更新答案。

数组开两倍。

#include <bits/stdc++.h>
using namespace std;
inline int read(){
	register char ch=getchar();register int x=0;
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return x;
}
typedef long long ll;
const int N=4e5+5;
int n,m,nxt[N],f[19][N];
struct J {
	int l,id,ans;ll r;
}a[N];
int main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++){
		a[i].l=read(),a[i].r=read(),a[i].id=i;
		if(a[i].l>a[i].r)a[i].r+=m;
	}
	for(int i=1;i<=n;i++)a[i+n].l=a[i].l+m,a[i+n].r=a[i].r+m,a[i+n].id=1e9;
	sort(a+1,a+n*2+1,[](J a,J b){return a.l<b.l;});
	for(int i=1,j=1;i<=n*2;i++){
		while(j<=n*2&&a[j].l<=a[i].r)j++;
		nxt[i]=j-1;
	}
	for(int i=1;i<=n*2;i++)f[0][i]=i;
	for(int i=1;i<=18;i++)
		for(int j=1;j<=n*2;j++)
			f[i][j]=f[i-1][nxt[f[i-1][j]]];
	for(int i=1;i<=n;i++){
		int ans=1e9,x=i,st=0;
		for(int j=18;~j;j--){
			if(a[f[j][x]].r-a[i].l>=m)ans=min(ans,st+(1<<j));
			else st+=(1<<j),x=nxt[f[j][x]];
		}
		a[i].ans=ans;
	}
	sort(a+1,a+n*2+1,[](J a,J b){return a.id<b.id;});
	for(int i=1;i<=n;i++)printf("%d ",a[i].ans);
}
posted @ 2022-05-14 19:42  pengyule  阅读(37)  评论(0编辑  收藏  举报