[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);
}