【BZOJ 3636】教义问答手册 (分治+整体二分+dp)

description

“汉中沃野如关中,四五百里烟蒙蒙。黄云连天夏麦熟,水稻漠漠吹秋风。”——摘自 黄裳《汉中行》
“泉岭精神不朽,汉中诸球永生。”——摘自《泉岭精神创立者语录》
“把神犇烤一烤,味道会更好。”——摘自《xhr语录》
“秀恩爱有利于身心健康!”——摘自《泉岭精神集大成者语录》
“楼上说的对!”——摘自《泉岭精神信徒语录合集》
“不会做积分,怎么找妹子!”——摘自《xhr语录》
“切实保护耕地以放置更多的哨戒炮。”——摘自《泉岭精神信徒语录合集》
“就算两个包子一起吃掉,也不能阻止我修筑梯田。”——摘自《泉岭精神创立者语录》
“我来自泉岭,他来自汉中,我们半道而逢。”——摘自《泉岭精神集大成者语录》
【问题描述】
作为泉岭精神的缔造者、信奉者、捍卫者、传承者,Pear决定印制一些教义问答手册,以满足泉岭精神日益增多的信徒。Pear收集了一些有关的诗选、语录,其中部分内容摘录在了【题目背景】里。这些语录是按出现的时间排好序的——Pear很喜欢这样的作风,于是决定在按时间排好序的基础上,选择部分语录,制作成若干本教义问答手册。
一共有N条语录。Pear决定从中选出某一段时间内的所有语录,在此基础上印制大小为L的若干本教义问答手册。Pear对印制的手册有如下要求:
1.每本手册必须包含这个区间内连续的恰好L条语录。
2.不同手册包含的语录不能相同。
3.每条语录有一个“主题相关程度”,这个数可正可负。Pear希望所有手册的语录的“主题相关程度”之和尽可能大。
例如,对于区间[3,15]和L=3,一种选择方法是:[4,6]+[9,11]+[12,14]。这三个区间长度都恰好为L,且互不重叠。
Pear并没有决定选哪段时间的语录,因此他有Q次询问。每次询问,给出两个数[l,r]表示候选语录的范围是第l条到第r条。你能回答出每个询问的最大“主题相关程度”之和么?
Input
第一行两个正整数N,L,含义如上所述。注意对于所有询问,L都是一样的。
第二行N个整数,绝对值<=10000。第i个数表示第i条语录的“主题相关程度”。
接下来Q行,每行两个正整数l和r,表示询问区间。
Output
输出Q行,每行表示这组询问的答案。注意,这个答案可以是0,如果区间负数过于多的话。
Sample Input
15 3
3 1 5 -2 3 -2 -2 2 2 2 0 3 2 -1 0
9
8 10
10 10
9 11
2 14
5 14
5 13
12 13
7 13
2 10

Sample Output
6
0
4
17
11
11
0
11
12
Hint

【数据范围】
对于10%的数据,N=1000,Q=1000,L<=50
对于另外20%的数据,N=100000,Q=100000,L<=5
对于另外20%的数据,N=100000,Q=100000,L<=10
对于100%的数据,N=100000,Q=100000,L<=50
Source
2014年国家集训队十五人互测

solution

从最原始的 O ( n 2 ) O(n^2) O(n2)暴力入手
f [ i ] [ j ] f[i][j] f[i][j]表示区间 [ i , j ] [i,j] [i,j]的答案最大值, s u m [ i ] sum[i] sum[i]表示前缀和
f [ i ] [ j ] = m a x ( f [ i ] [ j − 1 ] , f [ i ] [ j − L ] + s u m [ j ] − s u m [ j − L ] ) f[i][j]=max(f[i][j-1],f[i][j-L]+sum[j]-sum[j-L]) f[i][j]=max(f[i][j1],f[i][jL]+sum[j]sum[jL])
分治,分界线 m i d mid mid [ l , r ] [l,r] [l,r]分为左右两个区间

  • 长度为 L L L的一段完全属于左区间/右区间
  • 跨越了 m i d mid mid在左右区间都有的一段

第一种完全属于某半边区间的就直接分治递归处理
考虑第二种情况,对每一个 l ′ l' l预处理出在 [ l ′ , m i d ] [l',mid] [l,mid]区间选了若干个长度为 L L L的区间,且在末尾选了 x x x个数的最大值,然后对每一个 r ′ r' r预处理出在 [ m i d + 1 , r ‘ ’ ] [mid+1,r‘’] [mid+1,r]区间选了若干个长度为 L L L的区间,并在开头选了 x x x个数的最大值

f l [ i ] [ j ] fl[i][j] fl[i][j]:以 j j j为左端点,左区间末尾选了 i i i个数的最大值
f r [ i ] [ j ] fr[i][j] fr[i][j]:以 j j j为右端点,右区间开头选了 i i i个数的最大值

最后询问可以整体二分一并处理

思路好懂,主要是代码细节很ex人

code

#include <cstdio>
#include <iostream>
using namespace std;
#define maxn 100005
int n, L, Q;
int p[maxn], ql[maxn], qr[maxn], sum[maxn];//前缀和差分 
int fl[55][maxn], fr[55][maxn];
int Left[maxn], Right[maxn], ans[maxn];
//fl[i][j]:以j为左端点 在末尾选了i个数 的最大值 
//fr[i][j]:以j为右端点 在开头选了i个数 的最大值
//预处理时的最大值只加了完整L的区间值 
void calc_L( int l, int r ) {//对左区间进行预处理 
	for( int i = 0;i < min( r - l + 1, L );i ++ ) {//枚举跨越mid的在区间末尾选的长度i 
		fl[i][r - i + 1] = 0;
		for( int j = r - i;j >= l;j -- )//枚举区间的左端点j
			fl[i][j] = j + L - 1 > r - i ? 0 : max( fl[i][j + 1], fl[i][j + L] + sum[j + L - 1] - sum[j - 1] );
		//r-i就是不与枚举末尾长度i的起始点相交的第一个点
		//换言之[r-i+1,r]就是枚举的一个跨越mid的L区间在左半边的部分 
		//三目运算符判断是否以j为左端点时可以划分出一个完整的在区间的L且不交 (j~j+L-1)
	}
}
/*
fl[L]:一个跨越mid的长度L区间在左半边就有L个 也就相当于压根没跨越
fr[L]:同理也相当于压根没跨越
此情况恰好与fl[0],fr[0]差了一个L的区间划分循环
故可以由fl[0],fr[0]表示
*/
void calc_R( int l, int r ) {
	for( int i = 0;i < min( r - l + 1, L );i ++ ) {
		fr[i][l + i - 1] = 0;//[l,l+i-1]就是枚举的一个跨越mid的L划分区间在右半边的部分 
		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] + sum[j] - sum[j - L] );
		//三目运算符判断是否以j为右端点时可以划分出一个完整的在区间的L且不交 (j-L+1~j)
	} 
}

void solve( int Ql, int Qr, int l, int r ) {
	if( Ql > Qr || r - l + 1 < L ) return;
	int mid = ( l + r ) >> 1;
	int lenl = 0, lenr = 0;
	calc_L( l, mid );
	calc_R( mid + 1, r );
	for( int i = Ql;i <= Qr;i ++ ) {
		int id = p[i];
		if( qr[id] <= mid ) 
			Left[++ lenl] = id;
		else if( ql[id] > mid ) 
			Right[++ lenr] = id;
		else {
			ans[id] = fl[0][ql[id]] + fr[0][qr[id]];
			//不存在跨越mid的L区间的情况下 最大值
			//dp动态规划预处理已经做到局部最优解
			for( int j = max( 1, L - ( qr[id] - mid ) );j <= mid - ql[id] + 1 && j < L;j ++ )
				ans[id] = max( ans[id], ( mid - j < l ? 0 : fl[j][ql[id]] ) + ( mid + L - j + 1 > r ? 0 : fr[L - j][qr[id]] ) + sum[mid + L - j] - sum[mid - j] );
			/*
			mid-j mid+1+L-j都是两边不相交的第一个点
			 
			ps:注意括号问题 要把三目运算符整个括在一起 不然会错
			 
			该跨越mid的区间应为 mid-j+1 ~ mid+1 + L-j - 1  

			枚举跨越mid的L区间的左区间长度j=max(1,L-(qr[id]-mid))
			保证至少要有1个 且 该区间的右端点抵到整个询问区间的右端点qr[id]

			mid-ql[id]+1&&j<L
			保证至多有L-1个这样右边才至少有一个 且 该区间的最多只能抵到整个询问区间的左端点ql[id]

			判断一下mid-j<l没有
			因为只预处理了左端点>=l的
			其余部分应该全为0

			同理右边判断一下mid+L-j+1>r没有
			只处理了右端点<=r
			其余部分应该全为0

			然后加上跨越的长度为L的区间的a值和
			
			但是好像去掉判断也是AC的???不懂了 按道理不能去掉的啊???
			ans[id] = max( ans[id], fl[j][ql[id]] + fr[L - j][qr[id]] + sum[mid + L - j] - sum[mid - j + 1 - 1] );
			*/
		}
	}
	for( int i = 1;i <= lenl;i ++ ) p[Ql + i - 1] = Left[i];
	for( int i = 1;i <= lenr;i ++ ) p[Ql + lenl + i - 1] = Right[i];
	solve( Ql, Ql + lenl - 1, l, mid );
	solve( Ql + lenl, Ql + lenl + lenr - 1, mid + 1, r );
	//注意这里不再是solve(Ql+lenl,Qr,mid+1,r); 进行分类后有一些询问操作是已经处理了的 
}

int main() {
	scanf( "%d %d", &n, &L );
	for( int i = 1;i <= n;i ++ ) {
		scanf( "%d", &sum[i] );
		sum[i] += sum[i - 1];
	}
	scanf( "%d", &Q );
	for( int i = 1;i <= Q;i ++ ) {
		p[i] = i;
		scanf( "%d %d", &ql[i], &qr[i] );
	}
	solve( 1, Q, 1, n );
	for( int i = 1;i <= Q;i ++ )
		printf( "%d\n", ans[i] );
	return 0;
} 
//代码参考博客:https://www.cnblogs.com/Orz-IE/p/12039213.html 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值