[DarkBZOJ3636] 教义问答手册

前言

如何评价题解看不懂只能自己把这道题想出来。

明明就是懒得看题解啊喂!(#`O′)

个人感觉这个应该不算 \(\tt cdq\)分治,但是又不知道怎么分类,于是。。。

题目

DARKBZOJ

讲解

分治其实就是按一定顺序做就可以保证时间复杂度的暴力。

所以我们先考虑 \(O(n^2)\)\(\tt dp\),显然我们可以令 \(dp_{l,r}\) 表示区间 \([l,r]\) 的最大划分。

考虑优化。

我们注意到 \(L\le 50\),这给了我们乱搞的资本。

考虑将询问丢到各个区间里面分治,如果一个询问完全被一个大区间 \([l,r]\) 的子区间包含,给这个子区间就好。这里我们认为 \([l,r]\) 的子区间为 \([l,mid]\)\([mid+1,r]\) ,和线段树是一样的。

如果这个询问跨越了中间的边界 \(mid\) 怎么办?由于 \(L\) 很小,我们可以枚举它在左边的长度,当然在右边的长度也可以计算出来。中间越界的部分是一个长度为 \(L\) 的区间加,两边是可以预处理的一个 \(\tt dp\)

思路不难,细节有点多。

代码

//12252024832524
#include <cstdio>
#include <cstring>
#include <algorithm>
#define TT template<typename T>
using namespace std; 

typedef long long LL;
const int MAXN = 100005;
const int MAXL = 52;
int n,len,Q;
int a[MAXN],ans[MAXN];

LL Read()
{
	LL x = 0,f = 1;char c = getchar();
	while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
	while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
	return x * f;
}
TT void Put1(T x)
{
	if(x > 9) Put1(x/10);
	putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
	if(x < 0) putchar('-'),x = -x;
	Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}

struct Query
{
	int l,r,ID;
}q[MAXN],lq[MAXN],rq[MAXN];

int dpl[MAXL][MAXN],dpr[MAXL][MAXN];//这里的l,r指的是左边的区间和右边的区间 
//右边界空出i,j开始dp [j,mid-i];左边界空出i,j开始dp [mid+1+i,j]
void workl(int l,int r)
{
	for(int i = 0;i < len && r-i >= l;++ i)//细品这里取等的原因 
	{
		int R = r-i;
		for(int j = R+1;j >= l;-- j)
		{
			if(R-j+1 < len) dpl[i][j] = 0;
			else dpl[i][j] = Max(dpl[i][j+1],dpl[i][j+len] + a[j+len-1]);
		}
	}
} 
void workr(int l,int r)
{
	for(int i = 0;i < len && l+i <= r;++ i)
	{
		int L = l+i;
		for(int j = L-1;j <= r;++ j)
		{
			if(j-L+1 < len) dpr[i][j] = 0;
			else dpr[i][j] = Max(dpr[i][j-1],dpr[i][j-len] + a[j]);
		}
	}
}

void solve(int l,int r,int ql,int qr)
{
	if(ql > qr || r-l+1 < len) return;
	if(l == r)
	{
		for(int i = ql;i <= qr;++ i) ans[q[i].ID] = Max(a[l],0);
		return;
	}
	int mid = (l+r) >> 1,ln = 0,rn = 0;
	workl(l,mid); workr(mid+1,r);
	for(int i = ql;i <= qr;++ i)
	{
		if(q[i].r <= mid) lq[++ln] = q[i];
		else if(q[i].l > mid) rq[++rn] = q[i];
		else
		{
			ans[q[i].ID] = Max(0,dpl[0][q[i].l]+dpr[0][q[i].r]);
			int LL = Max(0,mid+len-q[i].r),RR = Min(len,mid-q[i].l+1);
			for(int j = LL;j <= RR;++ j)//枚举左边剩多少 
				ans[q[i].ID] = Max(ans[q[i].ID],dpl[j][q[i].l] + dpr[len-j][q[i].r] + a[mid+len-j]);
		}
	}
	for(int i = 1;i <= ln;++ i) q[ql+i-1] = lq[i];
	for(int i = 1;i <= rn;++ i) q[ql+ln+i-1] = rq[i];
	solve(l,mid,ql,ql+ln-1); solve(mid+1,r,ql+ln,ql+ln+rn-1);
}

int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n = Read(); len = Read();
	for(int i = 1;i <= n;++ i) a[i] = Read() + a[i-1];
	for(int i = n;i > len;-- i) a[i] -= a[i-len];
	Q = Read();
	for(int i = 1;i <= Q;++ i)
	{
		q[i].l = Read();
		q[i].r = Read();
		q[i].ID = i;
	}
	solve(1,n,1,Q);
	for(int i = 1;i <= Q;++ i) Put(ans[i],'\n');
	return 0;
}
posted @ 2021-03-06 17:41  皮皮刘  阅读(96)  评论(0编辑  收藏  举报