[SCOI2015]国旗计划
按照套路我们首先拆环成链,然后在对每个条线段往后平移 \(m\) 个位置复制一遍。可以发现这样一个事实,将环上包围一圈相当于在数轴上使用若干条线段覆盖一段长度为 \(m\) 的区间,并且只要某个线段在我们选择的线段当中,那么我们将这条线段看作起点往后一定还是能找到和原来一样的线段去覆盖。因此,题目要我们求的每条线段必选情况下的最小士兵数量,实际上就是以这条线段开头的最小士兵数量。既然我们要让士兵数量尽可能小,我们可以发现当开头选定时,接下来我们一定是选择一个左端点被当前线段覆盖的线段中右端点最靠右的线段,又因为题目中给的任意两个士兵的覆盖的区间不会是包含关系,因此我们将这些线段按照左端点从小到大排序,右端点也会是单调递增的,因此我们要找在某个左端点在区域内右端点最靠右的线段,相当于查找左端点最靠右的线段,于是对于每个开头的线段,我们直接双指针扫过去即可 \(O(n)\) 求出右边第下一个开始的线段。
于是我们就有了一个暴力,从每个点开始不断去跳到右边下一个线段知道已经覆盖了长度为 \(m\) 的区间为止。可以发现这个暴力的本质其实是再不断跳点直到第一个满足条件的位置的过程,因为没有修改,这是一个很经典的可以使用倍增优化的问题,于是我们直接使用倍增优化即可。
#include<bits/stdc++.h>
using namespace std;
#define N 400000 + 5
#define M 20 + 5
#define int long long
#define rep(i, l, r) for(int i = l; i <= r; ++i)
#define dep(i, l, r) for(int i = r; i >= l; --i)
struct node{
int l, r, id;
}a[N];
int n, m, l, r, ans[N], dp[N][M];
int read(){
char c; int x = 0, f = 1;
c = getchar();
while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
bool cmp(node a, node b){
return a.l < b.l;
}
int solve(int p){
int R = a[p].l + m, ans = 0;
dep(i, 0, 18) if(a[dp[p][i]].r < R && dp[p][i]) ans += (1 << i), p = dp[p][i];
return ans + 2;
}
signed main(){
n = read(), m = read();
rep(i, 1, n){
l = read(), r = read(); if(l > r) r = r + m;
a[i].l = l, a[i].r = r, a[i].id = i, a[i + n].l = l + m, a[i + n].r = r + m;
}
sort(a + 1, a + 2 * n + 1, cmp);
int j = 1;
rep(i, 1, 2 * n){
while(j <= 2 * n && a[j].l <= a[i].r) ++j; --j;
dp[i][0] = j;
}
rep(j, 1, 18) rep(i, 1, 2 * n) dp[i][j] = dp[dp[i][j - 1]][j - 1];
rep(i, 1, n) ans[a[i].id] = solve(i);
rep(i, 1, n) printf("%lld ", ans[i]);
return 0;
}