【bzoj4444】[Scoi2015]国旗计划 倍增
题目描述
给出一个圈和若干段,问:对于所有的 $i$ ,选择第 $i$ 段的情况下,最少需要选择多少段(包括第 $i$ 段)能够覆盖整个圈?
输入
第1行,包含2个正整数N,M,分别表示边防战士数量和边防站数量。
随后n行,每行包含2个正整数。其中第i行包含的两个正整数Ci、Di分别表示i号边防战士常驻的两个边防站编号,
Ci号边防站沿顺时针方向至Di号边防站力他的奔袭区间。数据保证整个边境线都是可被覆盖的。
输出
输出数据仅1行,需要包含n个正整数。其中,第j个正整数表示j号边防战士必须参加的前提下至少需要
多少名边防战士才能顺利地完成国旗计划
样例输入
4 8
2 5
4 7
6 1
7 3
样例输出
3 3 4 3
题解
倍增
如果将选择的区间按照右端点正方向顺序考虑的话,那么如果选择了某个区间,下一个区间的选择一定是所有左端点小于等于该区间右端点中,右端点最靠后的那一个。
因此首先断环成链,然后选择区间 $[l,r]$ 后,下一个选择就应该是左端点在 $[1,r]$ 范围内,右端点最靠后的。
所以对于每一个区间 $[l,r]$ ,在 $l$ 位置上加入 $r$ ,然后求前缀最大值即可得到每个位置选上一个区间后最远能够覆盖到哪。
我们要求的是覆盖整个圈,因此可以考虑倍增算法,预处理出 $f[i][j]$ 表示从 $j$ 位置选择 $2^i$ 段区间最远能够覆盖到哪。那么上面的全椎最大值就是 $f[0][j]$ 。
根据递推式 $f[i][j]=f[i-1][f[i-1][j]]$ 预处理出 $f$ 数组,然后倍增求解。从大到小枚举 $i$ ,如果加入一段不能覆盖整个圈则加入,否则不加入。最后加上2(本身+无限逼近后剩余的一段)即为答案。
注意一下区间跨越 $m$ 的处理 ,详见代码。
时间复杂度 $O(n\log n)$
#include <cstdio> #include <cstring> #include <algorithm> #define N 200010 #define pos(x) lower_bound(v + 1 , v + m + 1 , x) - v using namespace std; int a[N] , b[N] , v[N << 1] , f[20][N << 2]; int main() { int n , m = 0 , i , j , t , ans; scanf("%d%*d" , &n); for(i = 1 ; i <= n ; i ++ ) scanf("%d%d" , &a[i] , &b[i]) , v[++m] = a[i] , v[++m] = b[i]; sort(v + 1 , v + m + 1); for(i = 1 ; i <= n ; i ++ ) { a[i] = pos(a[i]) , b[i] = pos(b[i]); if(a[i] < b[i]) { f[0][a[i]] = max(f[0][a[i]] , b[i]); f[0][a[i] + m] = max(f[0][a[i] + m] , b[i] + m); } else { f[0][1] = max(f[0][1] , b[i]); f[0][a[i]] = max(f[0][a[i]] , b[i] + m); f[0][a[i] + m] = max(f[0][a[i] + m] , m << 1); } } for(i = 1 ; i <= m << 1 ; i ++ ) f[0][i] = max(f[0][i] , f[0][i - 1]); for(t = 1 ; (1 << t) <= m << 1 ; t ++ ) for(i = 1 ; i <= m << 1 ; i ++ ) f[t][i] = f[t - 1][f[t - 1][i]]; for(i = 1 ; i <= n ; i ++ ) { ans = 0; if(a[i] < b[i]) a[i] += m; for(j = t - 1 ; ~j ; j -- ) if(f[j][b[i]] < a[i]) ans += (1 << j) , b[i] = f[j][b[i]]; printf("%d" , ans + 2); if(i < n) printf(" "); } return 0; }