给定正整数 m m m 以及长度为 n n n 的序列对 ( a i , b i ) (a_i,b_i) (ai,bi),你需要将它分为连续的若干段,满足以下2个条件:
① 若 i < j i
② 每一段的 a a a的最大值之和 ≤ m ≤m ≤m。
在此基础上,你需要最小化每一段的 b b b的和的最大值。
第一行两个正整数 n , m n,m n,m,接下来 n n n 行每行两个正整数 a i , b i a_i,b_i ai,bi。
一行一个整数表示答案。
4 6
4 3
3 5
2 5
2 4
9
【数据规模】
数据编号 | n ≤ n≤ n≤ | 特殊性质1 | 特殊性质2 |
---|---|---|---|
1 1 1 | 1000 1000 1000 | 无 | 无 |
2 2 2 | |||
3 3 3 | 100000 100000 100000 | a i a_i ai在 [ 1 , 1 0 9 ] [1,10^9] [1,109]内均匀随机 | m i n ( b i ) > m a x ( a i ) min(b_i)>max(a_i) min(bi)>max(ai) |
4 4 4 | a [ i ] > a [ i + 1 ] a[i]>a[i+1] a[i]>a[i+1] | ||
5 5 5 | 无 | ||
6 6 6 | a i a_i ai在 [ 1 , 1 0 9 ] [1,10^9] [1,109]内均匀随机 | b i b_i bi在 [ 1 , 1 0 9 ] [1,10^9] [1,109]内均匀随机 | |
7 7 7 | 无 | ||
8 8 8 | a [ i ] > a [ i + 1 ] a[i]>a[i+1] a[i]>a[i+1] | ||
9 9 9 | 无 | ||
10 10 10 |
对于100%的数据, n ≤ 100000 , m ≤ 1 0 12 , 1 ≤ a i , b i ≤ 2 × 1 0 9 n≤100000,m≤10^{12},1≤a_i,b_i≤2×10^9 n≤100000,m≤1012,1≤ai,bi≤2×109。
【时间限制】
3000 ms
【内存限制】
262144 KB
首先,观察条件1,显然可以发现条件1等价于:若 i < j i
如果我们将数列分成了若干段最长的区间,就可以在这些区间中拆分,以寻求最优解。
我们可以枚举区间起点 i i i,并不断扩大右端点,得到区间终点 e d ed ed 。
假设我们当前得到的区间是[ i , j i,j i,j]。
若 ∃ \exists ∃ x ∈ [ i , j ] x\in [i,j] x∈[i,j], y ∈ [ j + 1 , n ] y\in [ j+1,n ] y∈[j+1,n], b x ≤ a y b_x \leq a_y bx≤ay, 则 x , y x,y x,y在一个区间,显然可以将区间扩展成 [ i , y ] [i,y] [i,y],否则 j j j就是我们需要的 e d ed ed 。
显然,这个判断条件可以改成 m i n i ≤ x ≤ j { b x } ≤ m a x j ≤ y ≤ e d { a y } min_{i\leq x\leq j} \lbrace b_x \rbrace \leq max_{j \leq y \leq ed} \lbrace a_y \rbrace mini≤x≤j{bx}≤maxj≤y≤ed{ay}, 于是预处理 b b b数组的区间最小值,以及 a a a数组的后缀最大值。
我们可以将这若干段区间看成一个数对[ A i , A j A_i,A_j Ai,Aj],表示原序列中 A i A_i Ai到 A j A_j Aj位置是一段合法区间。
现在已经确定了一些满足条件1的合法区间,且这些区间都是最长的。我们现在要做的就是划分这些区间,使其满足在满足条件2的情况下,最小化每一段的 b b b的和的最大值。
显然使用二分答案,对于当前二分出来的 m i d mid mid值,我们对 b b b的和的要求是不大于 m i d mid mid,我们可以使用 D P DP DP来完成判断。
设 S i = Σ j = 1 j ≤ n b j S_i = \Sigma_{j=1}^{j \leq n} b_j Si=Σj=1j≤nbj,即求 b b b数组的前缀和。
设 f [ i ] f[i] f[i]表示把前 i i i个数对分成若干段,在每一段的 b b b的和都不超过 m i d mid mid的情况下,每一段的最大的 a a a的和的最小值是多少,转移方程如下:
f [ i ] = m i n 0 ≤ j < i 且 S i − S j ≤ m i d { f [ j ] + m a x j < k ≤ i { a k } } f[i] = min_{0 \leq j < i且S_i - S_j \leq mid} \lbrace f[j]+max_{j < k \leq i} \lbrace a_k \rbrace \rbrace f[i]=min0≤j<i且Si−Sj≤mid{f[j]+maxj<k≤i{ak}}
显然,这个 D P DP DP方程是 O ( n 2 ) O(n^2) O(n2)的,再加上二分的 l o g n log_n logn," 妥妥 " 的 20 20 20分(不知道我的数据出的怎么样,欢迎大佬卡我的数据)。
考虑优化。。。。。
先来2个我也不知道怎么分析出来的式子,如果当前 j j j是最优解,那么一定满足这2个式子中的一个:
证明:
反证法。
假设2个条件都不满足,即 a j < m a x j ≤ k ≤ i { a k } a_j
则 m a x j ≤ k ≤ i ≤ m i d max_{j \leq k \leq i} \leq mid maxj≤k≤i≤mid,所以有:
f [ j − 1 ] + m a x j ≤ k ≤ i { a k } ≤ f [ j ] + m a x j ≤ k ≤ i { a k } f[j-1] + max_{j \leq k \leq i} \lbrace a_k \rbrace \leq f[j] + max_{j \leq k \leq i} \lbrace a_k \rbrace f[j−1]+maxj≤k≤i{ak}≤f[j]+maxj≤k≤i{ak}
决策 j − 1 j-1 j−1 比 j j j 更优,与假设矛盾,原命题成立。
证毕。
显然,我们可以对上述2个条件分别维护,然后取最优决策就行了。
对于条件2,我们只要维护一个指针,指向满足 s i − s j ≤ m i d s_i - s_j \leq mid si−sj≤mid 的最小的 j j j,并不断的更新右移就可以了。
对于条件1,我们将满足条件1的决策的 j j j 加入一个队列,使得决策点 j j j 单调递增,显然,根据引理1, a j a_j aj应该是单调递减的。若想让队尾加入一个新决策点 j 0 j_0 j0,对于队尾的决策点 j j j,有 a j < a j 0 a_j < a_{j_0} aj<aj0,决策 j j j不满足条件,需要将队尾元素弹出,不断把不合法的元素弹出,最后将决策点 j 0 j_0 j0加入队尾,这样就维护了队列中 j j j单调递增, a j a_j aj单调不递增的性质。
但是这个队列没有维护 f [ j ] + m a x j < k ≤ i { a k } f[j]+max_{j < k \leq i} \lbrace a_k \rbrace f[j]+maxj<k≤i{ak}的单调性。
假设在得到 f [ i ] f[i] f[i]之前,先将决策 i i i加入了队列(插入了队尾),对于不在队尾的一个决策 j j j, m a x j < k ≤ i { a k } max_{j
我们可以维护一个数据结构维护费队尾决策的 f [ j ] + m a x j < k ≤ i { a k } f[j]+max_{j < k \leq i} \lbrace a_k \rbrace f[j]+maxj<k≤i{ak},支持查询 m a x j < k ≤ i { a k } max_{j
每次就只需要在数据结构中查询 f [ j ] + m a x j < k ≤ i { a k } f[j]+max_{j < k \leq i} \lbrace a_k \rbrace f[j]+maxj<k≤i{ak}的最大值来更新 f [ i ] f[i] f[i]。
可以使用线段树,平衡树等神仙数据结构,但是我都不会,因此我使用了STL 的 m u l t i s e t multiset multiset。
安利一篇dalao 的 m u l t i s e t multiset multiset b l o g blog blog,不会 m u l t i s e t multiset multiset的童鞋可以自行学习。
https://blog.csdn.net/sodacoco/article/details/84798621
算法复杂度 O ( n l o g n 2 ) O(n \ log_n^2) O(n logn2),即二分判断 O ( n l o g n ) O(n \ log_n) O(n logn), m u l t i s e t multiset multiset查询,插入,删除 O ( l o g n ) O(log_n) O(logn)。
#include
#pragma GCC optimize(2)
using namespace std;
#define LL long long
const int N = 2e5 + 7;
const int M = 20;
LL n, m;
LL f[N] = {};
LL q[N];
int head = 0, tail = 0, tot = 0;
LL a[N], b[N], s[N], logval[N];
LL Max_a[N], Min_b[M][N] = {};
char ordin[11000] = {};
multiset <LL> Min_set;
//Max_a 表示a数组后缀最大值的下标
//Max_b 表示b数组1...(1 << i) - 1 的最小值
inline LL Max(int x,int y) {
return a[x] > a[y] ? x : y;
}
inline LL getMin(int l, int r) {
LL k = logval[r-l+1];
return min(Min_b[k][l], Min_b[k][r - (1 << k) + 1]);
}
inline bool check(LL mid) {
memset(f, 0x3f, sizeof f);
Min_set.clear();
head = 1, tail = 1, q[1] = f[0] = 0;
int le = 0;
for (int i = 1; i <= n; i ++) {
while (head <= tail && s[i] - s[q[head]] > mid) { // 弹出队首Si-Si > mid 的不合法方案
if (head < tail) Min_set.erase(Min_set.find(f[q[head]] + a[q[head+1]]));
head ++;
}
while (head <= tail && a[q[tail]] < a[i]) { //维护队列a的单调不增性
if (head < tail) Min_set.erase(Min_set.find(f[q[tail-1]] + a[q[tail]]));
-- tail;
}
if (head <= tail) Min_set.insert(f[q[tail]] + a[i]); //维护原队尾的决策转移值
q[++ tail] = i;
while (le < i && s[i] - s[le] > mid) ++ le; //维护le指针,用满足条件2的决策来更新f[i]
if (le == q[head]) f[i] = f[le] + a[q[head+1]]; //若le指向队首元素,则取队首后一个元素,反之队首元素
else f[i] = f[le] + a[q[head]];
if (head < tail) f[i] = min(f[i], * Min_set.begin()/*把Min_set的第一个元素取出来*/); //试图选择条件1的最优决策
}
return f[n] <= m;
}
int main() {
freopen("sequence.in","r",stdin);
freopen("sequence.out","w",stdout);
// for (int ii = 1; ii <= 20; ii ++) {
// sprintf(ordin,"%s%d.in","sequence",ii);
// freopen(ordin,"r",stdin);
// sprintf(ordin,"%s%d.out","sequence",ii);
// freopen(ordin,"w",stdout);
scanf("%lld %lld",&n,&m);
memset(logval, 0, sizeof logval);
logval[0] = -1;
tot = 0;
for (int i = 1; i <= n; i ++) {
scanf("%lld %lld",&a[i],&b[i]);
logval[i] = logval[i >> 1] + 1;
Min_b[0][i] = b[i];
}
Max_a[n] = n;
for (int i = n - 1; i; -- i) {
Max_a[i] = Max(Max_a[i+1], i);
}
//a数组的后缀最大值
for (int j = 1; j <= M; j ++) {
for (int i = 1; i + (1 << j) - 1 <= n; i ++)
Min_b[j][i] = min(Min_b[j-1][i], Min_b[j-1][i + (1 << (j - 1))]);
}
//用st表来预处理b数组
for (int i = 1; i <= n;) {
int j;
for (j = i; j < n && a[Max_a[j+1]] >= getMin(i, j); j = Max_a[j+1]);
a[++ tot] = a[i];
b[tot] = b[i];
for (i = i + 1; i <= j; i ++) {
a[tot] = max(a[tot], a[i]);
b[tot] += b[i];
}
}
n = tot;
//缩区间为点对
LL l = 0, r = 0, mid , ans = 0;
for (int i = 1; i <= n; i ++) {
s[i] = s[i-1] + b[i];
l = max(l, b[i]);
r += b[i];
}
while (l <= r) {
mid = l + r >> 1;
if (check(mid)) r = mid - 1, ans = mid;
else l = mid + 1;
}
printf("%lld", ans);
// }
return 0;
}