CF1172F

P5609 [Ynoi2013]对数据结构的爱 / CF1172F [* hard]

题意见 link

Solution

查询的答案其实就是区间和减去 \(k\times p\)\(k\) 表示被减 \(p\) 的次数。

考虑线段树,我们考虑做完一个区间后肯定会剩余和 \(S\),然后我们将 \(S\) 输入给一个区间后肯定会减去若干个 \(p\)

假设区间长度为 \(r-l+1\),那么我们至多减去 \(r-l+1\)\(p\),同时,我们减去的 \(p\) 的数量关乎于初始输入的参数,这样会形成一个分段函数,我们假设进行了预处理,那么就可以直接通过二分来进行查询。

所以我们的处理思路就是将这些分段函数预处理出来,然后查询就直接二分 \(\log n\) 个区间即可,复杂度 \(\mathcal O(q\log^2 n)\)

考虑预处理,我们将小区间合并为大区间。

\(c_i\) 表示最小的 \(S\) 使得输入进来后被减去了 \(i\)\(p\)

我们知道了 \(c_{[\rm l,mid]},c_{[\rm mid+1,r]}\),我们希望得到 \(c_{[l,r]}\)

对于一组 \(x,y\),我们可以合并得到 \(c_{x+y}\),假设此时以 \(S\) 作为初值输入进来,我们有 \(S-x\times p+sum_{l}\ge c_y,S\ge c_x\) 那么就可以更新一次答案。

于是有 \(c_{x+y}=\min(c_{x+y},\max(c_y+x\times p-sum_l,c_x))\)

然后显然我们这样不一定会得到到最优解,但是这个限制比最优解要求的限制更松,同时一定能够得到最优解。

基于这样的考量,我们希望计算 \(x+y\) 相同的对子中最小的 \(\max(c_x,c_y+x\times p-sum_l)\)

事实上,\(c_x\) 是单调的,\(c_y\) 是单调的,我们的决策必然是扫到一个 \(x\) 使得 \(c_y+x\times p-sum_l\) 小于他,以及严格大于他,更新两次答案。

这样,当 \(c_x\) 增大的过程,我们只需要证明相应的 \(y\) 会单调,即 \(c_{y}-c_{y-1}\ge p\)(感性上看着挺对的)

这样就可以通过双指针来合并 \(c\) 数组了,复杂度 \(\mathcal O(q\log^2 n+n\log n)\)

  • 可以考虑 \(x+y=k\) 合并了一次答案,此时 \((x+1)+(y-1)\) 如何无法对答案产生贡献。

\(Code:\)

#include<bits/stdc++.h>
using namespace std ;
#define Next( i, x ) for( register int i = head[x]; i; i = e[i].next )
#define rep( i, s, t ) for( register int i = (s); i <= (t); ++ i )
#define drep( i, s, t ) for( register int i = (t); i >= (s); -- i )
#define re register
#define vi vector<int>
#define ls(x) (x << 1)
#define rs(x) (x << 1 | 1)
#define pb push_back
#define int long long
int gi() {
	char cc = getchar() ; int cn = 0, flus = 1 ;
	while( cc < '0' || cc > '9' ) {  if( cc == '-' ) flus = - flus ; cc = getchar() ; }
	while( cc >= '0' && cc <= '9' )  cn = cn * 10 + cc - '0', cc = getchar() ;
	return cn * flus ;
}
const int N = 1e6 + 5 ; 
const int inf = 1e18 + 7 ; 
int n, m, p, a[N], S ; 
struct Tr {
	int sum, len ; vi f ; 
} tr[N << 2] ; 
void pushup(int x) {
	vi l = tr[ls(x)].f, r = tr[rs(x)].f ;
	vi c ; c.resize(tr[x].len + 1) ;
	rep( i, 0, tr[x].len ) c[i] = inf ; int j = 0 ; 
	for(int i = 0; i < l.size(); ++ i) {
		int d = i * p - tr[ls(x)].sum ; if(j) -- j ; 
		for(; j < r.size(); ++ j) {
			int res = max( l[i], r[j] + d ) ;
			c[i + j] = min( c[i + j], res ) ; 
			if( i != (l.size() - 1) ) {
				int mx = l[i + 1] - d ; 
				if( mx <= r[j] ) { if(j) -- j ; break ; }
			}
		}
	} tr[x].f = c, tr[x].sum = tr[ls(x)].sum + tr[rs(x)].sum ; 
}
void build(int x, int l, int r) {
	tr[x].len = r - l + 1 ; 
	if( l == r ) { tr[x].sum = a[l], tr[x].f.pb(-inf), tr[x].f.pb(p - a[l]) ; return ; }
	int mid = (l + r) >> 1 ; 
	build(ls(x), l, mid), build(rs(x), mid + 1, r), pushup(x) ; 
}
void query(int x, int l, int r, int ql, int qr) {
	if( ql <= l && r <= qr ) {
		int res = upper_bound(tr[x].f.begin(), tr[x].f.end(), S) - tr[x].f.begin() - 1 ;
		S = S + tr[x].sum - res * p ; return ; 
	}
	if( qr < l || ql > r ) return ; 
	int mid = (l + r) >> 1 ; 
	query(ls(x), l, mid, ql, qr), query(rs(x), mid + 1, r, ql, qr) ; 
}
void Q(int l, int r) {
	S = 0, query(1, 1, n, l, r), printf("%lld\n", S ) ; 
}
signed main()
{
	n = gi(), m = gi(), p = gi() ; 
	rep( i, 1, n ) a[i] = gi() ; 
	build(1, 1, n) ; int l, r ; 
	while( m -- ) l = gi(), r = gi(), Q(l, r) ;
	return 0 ;
}
posted @ 2020-10-10 15:58  Soulist  阅读(111)  评论(0编辑  收藏  举报