BZOJ 3636 教义问答手册 (分治)

题意

一个整数数列,多次询问某段区间 [ l i , r i ] [l_i,r_i] [li,ri]内,选出若干个长度为 L L L且不相交的连续段使选出来的数和最大。

分析

首先想朴素的区间 D P DP DP
f [ i ] [ j ] f[i][j] f[i][j]表示区间 [ i , j ] [i,j] [i,j]的答案。 O ( n 2 ) O(n^2) O(n2)转移比较显然。
f [ i ] [ j ] = m a x ( f [ i ] [ j − 1 ] , f [ i ] [ j − L ] + ∑ k = j − L + 1 j a [ j ] ) f[i][j]=max(f[i][j-1],f[i][j-L]+\sum_{k=j-L+1}^ja[j]) f[i][j]=max(f[i][j1],f[i][jL]+k=jL+1ja[j])
为了方便我们把 ∑ k = j − L + 1 j a [ j ] \sum_{k=j-L+1}^ja[j] k=jL+1ja[j]记作 b [ j ] b[j] b[j],则
f [ i ] [ j ] = m a x ( f [ i ] [ j − 1 ] , f [ i ] [ j − L ] + b [ j ] ) f[i][j]=max(f[i][j-1],f[i][j-L]+b[j]) f[i][j]=max(f[i][j1],f[i][jL]+b[j])
空间和时间都会爆。然后想想多组询问怎么做。

发现 L ≤ 50 L\le50 L50,于是就有巧妙的分治做法。

对于区间 [ q L , q R ] [qL,qR] [qL,qR],中点是 m i d mid mid。我们把跨越 m i d mid mid的询问拿出来求答案,没有跨越的分治下去,就变成了子问题。

那么对于一个询问 [ q L , q R ] [qL,qR] [qL,qR],它的答案有两种情况。

  • 一种是没有选跨越 m i d mid mid的连续段,那么就是 [ q L , m i d ] [qL,mid] [qL,mid] [ m i d + 1 , q R ] [mid+1,qR] [mid+1,qR]的答案加起来。
  • 另一种是跨越了 m i d mid mid的。因为 L L L很小我们枚举跨越 m i d mid mid的一段在左边有多长,记作 j j j,则右边长为 L − j L-j Lj。那么有 1 < j < L , 1 < L − j < L 1<j<L,1<L-j<L 1<j<L,1<Lj<L
    那么答案就是 [ q L , m i d − j ] [qL,mid-j] [qL,midj]的答案加上 [ m i d + 1 + L − j , q R ] [mid+1+L-j,qR] [mid+1+Lj,qR]的答案再加上中间一段的答案 b [ m i d + L − j ] b[mid+L-j] b[mid+Lj]

发现上面需要算答案的区间只有 O ( n L ) O(nL) O(nL)级别的且连续,可以直接预处理。

然后就做完了。时间复杂度 O ( n L log ⁡ n ) O(nL\log n) O(nLlogn)。具体实现见代码。

CODE

#include <bits/stdc++.h>
using namespace std;
char cb[1<<18],*cs,*ct;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<18,stdin),cs==ct)?0:*cs++)
inline void rd(int &x) {
	x = 0; char ch; int flg=1; while(!isdigit(ch=getc()))if(ch=='-')flg=-flg;
	do x=x*10+ch-'0'; while(isdigit(ch=getc())); x *= flg;
}
const int MAXN = 100005;
const int MAXL = 55;
int n, m, L, a[MAXN], ans[MAXN];
struct query { int l, r, id; }q[MAXN], tmp1[MAXN], tmp2[MAXN];
int fL[MAXL][MAXN], fR[MAXL][MAXN];
inline void workL(int l, int r) {
	for(int i = 0; i <= r-l && i < L; ++i) {
		fL[i][r-i+1] = 0;
		for(int j = r-i; j >= l; --j)
			fL[i][j] = j+L-1 > r-i ? 0 : max(fL[i][j+1], fL[i][j+L] + a[j+L-1]);
	}
}
inline void workR(int l, int r) {
	for(int i = 0; i <= r-l && i < L; ++i) {
		fR[i][l+i-1] = 0;
		for(int j = l+i; j <= r; ++j)
			fR[i][j] = j-L+1 < l+i ? 0 : max(fR[i][j-1], fR[i][j-L] + a[j]);
	}
}
void cdq(int l, int r, int ql, int qr) {
	if(ql > qr || r-l+1 < L) return;
	int mid = (l + r) >> 1, it1 = 0, it2 = 0;
	workL(l, mid); workR(mid+1, r);
	for(int i = ql; i <= qr; ++i) {
		if(q[i].r <= mid) tmp1[++it1] = q[i];
		else if(q[i].l > mid) tmp2[++it2] = q[i];
		else {
			ans[q[i].id] = fL[0][q[i].l] + fR[0][q[i].r];
			for(int j = max(1, L-(q[i].r-mid)); j <= mid-q[i].l+1 && j < L; ++j)
				ans[q[i].id] = max(ans[q[i].id], (mid-j < l ? 0 : fL[j][q[i].l]) + (mid+L-j+1 > r ? 0 : fR[L-j][q[i].r]) + a[mid+L-j]);
		}
	}
	for(int i = 1; i <= it1; ++i) q[ql+i-1] = tmp1[i];
	for(int i = 1; i <= it2; ++i) q[ql+it1+i-1] = tmp2[i];
	cdq(l, mid, ql, ql+it1-1); cdq(mid+1, r, ql+it1, ql+it1+it2-1);
}
int main () {
	rd(n), rd(L);
	for(int i = 1; i <= n; ++i) rd(a[i]), a[i] += a[i-1];
	for(int i = n; i >= L; --i) a[i] -= a[i-L];
	rd(m);
	for(int i = 1; i <= m; ++i) rd(q[i].l), rd(q[i].r), q[i].id = i;
	cdq(1, n, 1, m);
	for(int i = 1; i <= m; ++i) printf("%d\n", ans[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值