【高手训练】1790:序列划分

【题目描述】

给定正整数 m m m 以及长度为 n n n 的序列对 ( a i , b i ) (a_i,b_i) (ai,bi),你需要将它分为连续的若干段,满足以下2个条件:

① 若 i < j ii<j i i i j j j不在一段中,则 b i > a j b_i>a_j bi>aj

② 每一段的 a a a的最大值之和 ≤ m ≤m m

在此基础上,你需要最小化每一段的 b b b的和的最大值。

【输入】

第一行两个正整数 n , m n,m nm,接下来 n n n 行每行两个正整数 a i , b i a_i,b_i aibi

【输出】

一行一个整数表示答案。

【输入样例】

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 n100000m10121aibi2×109

【时间限制】

3000 ms

【内存限制】

262144 KB


序列划分.solution

首先,观察条件1,显然可以发现条件1等价于:若 i < j ii<j b i ≤ a j b_i \leq a_j biaj , 则 i i i j j j 在一段中。

如果我们将数列分成了若干段最长的区间,就可以在这些区间中拆分,以寻求最优解。

我们可以枚举区间起点 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 bxay, 则 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 minixj{bx}maxjyed{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=1jnbj,即求 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]=min0j<iSiSjmid{f[j]+maxj<ki{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个式子中的一个:

  • a j = m a x j ≤ k ≤ i { a k } a_j = max_{j \leq k \leq i} \lbrace a_k\rbrace aj=maxjki{ak}
  • S i − S j − 1 > m i d S_i - S_{j-1} > mid SiSj1>mid

证明:

反证法。

假设2个条件都不满足,即 a j < m a x j ≤ k ≤ i { a k } a_j aj<maxjki{ak} s i − s j − 1 ≤ m i d s_i - s_{j-1} \leq mid sisj1mid

m a x j ≤ k ≤ i ≤ m i d max_{j \leq k \leq i} \leq mid maxjkimid,所以有:

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[j1]+maxjki{ak}f[j]+maxjki{ak}

决策 j − 1 j-1 j1 j j j 更优,与假设矛盾,原命题成立。

证毕。

显然,我们可以对上述2个条件分别维护,然后取最优决策就行了。

  • 对于条件2,我们只要维护一个指针,指向满足 s i − s j ≤ m i d s_i - s_j \leq mid sisjmid 的最小的 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<ki{ak}的单调性。

    假设在得到 f [ i ] f[i] f[i]之前,先将决策 i i i加入了队列(插入了队尾),对于不在队尾的一个决策 j j j m a x j < k ≤ i { a k } max_{jmaxj<ki{ak},其实就是它在队列中的下一个元素值。所以在 i i i插入队尾前,我们可以用 i i i维护原来的决策 j j j m a x j < k ≤ i { a k } max_{jmaxj<ki{ak}

    我们可以维护一个数据结构维护费队尾决策的 f [ j ] + m a x j < k ≤ i { a k } f[j]+max_{j < k \leq i} \lbrace a_k \rbrace f[j]+maxj<ki{ak},支持查询 m a x j < k ≤ i { a k } max_{jmaxj<ki{ak}最小值,并且支持插入弹出固定元素。

    每次就只需要在数据结构中查询 f [ j ] + m a x j < k ≤ i { a k } f[j]+max_{j < k \leq i} \lbrace a_k \rbrace f[j]+maxj<ki{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; 
} 

你可能感兴趣的:(题解)