题意:给序列D和L。假设有一个初始价值x0,则经历第i天后价值变为min(x0+D[i],L[i]),记F(i,j,x0)表示以初始代价x0依次经过第i天到第j天后的价值。每次询问给出l,r,x0,求max(F(i,j,x0)),其中[i,j]是子串[l,r]的子串。
这个题的最重要的性质在于:
一、对于a>=b,F(i,j,a)>=F(i,j,b);
二、记G(i,j)为F(i,j,inf),其中inf是正无穷,S(i,j)为i到j的D值之和。则有F(i,j,x0)=min( G(i,j), x0+S(i,j) )。
以上两个式子基本上是这个题的瓶颈,发现之后剩下的部分就很容易了。
推论:对于询问的l,r,如果两个子串都被包含在[l,r]中,且有G1>=G2且S1>=S2,显然第二个子串是一定不会取到的(由性质二得到)。
我们考虑分块。每个块的大小为根号n,则每个块内的子串总数为O(n)个。对于每个块内我们处理出它所有的子串的G值和S值,并排序,并用个单调栈剔除那些G值和S值都比另一个子串小的子串(推论保证了这样做的正确性)。这样做之后剩下的区间一定保证G单增,S单减,则显然这些排好序的子串中左边一部分对答案的贡献为G值,右边一部分的贡献为S+x0值,二分找到分界点,答案一定在分界点处。
块以内的答案可以通过以上方式得到,块之间的需要动态维护一个Y值,表示之前的块结尾之后所能保留的最大的价值,我们还需要维护每个块的所有前缀和后缀,用前缀子串来回答Y能产生的最大价值,用所有子串来回答x0能产生的最大价值。然后要进入下一个块,需要更新Y值。新的Y值可能来源:原来的Y完整的走完这个块之后的价值,x0通过这个块的后缀所能产生的最大价值,以及x0(表示这个块以及之前都不取),这三个值取个最大值就是新的Y值。
边界处理比较恶心,我的实现得不是很优秀。。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define erp(i,a,b) for(int i=a;i>=b;--i)
using namespace std;
inline void gmax(int&a, const int&b) {if(a<b)a=b;}
inline void gmin(int&a, const int&b) {if(a>b)a=b;}
const int MAXN = 50005;
const int inf = 0x3f3f3f3f;
int N, Q, bsz;
int D[MAXN], S[MAXN], L[MAXN], be[MAXN];
struct Node {
int g, s;
Node() {}
Node(int a,int b):g(a), s(b){}
bool operator < (const Node&b) const {
if (g^b.g) return g<b.g;
return s < b.s;
}
} sta[MAXN];
struct Block
{
Node a[MAXN], pre[233], suf[233]; //所有子串,前缀,后缀
int sz, G, S, pn, sn;
void push(Node t) { a[++sz] = t; }
void pushp(Node t) { pre[++pn] = t; }
void pushs(Node t) { suf[++sn] = t; }
void init()
{
sort(a+1, a+sz+1);
int tp = 0;
rep(i, 1, sz)
{
while (tp&&a[i].s>=sta[tp].s) tp--;
sta[++tp] = a[i];
}
rep(i, 1, tp) a[i] = sta[i];
sz = tp;
sort(pre+1, pre+pn+1);
tp = 0;
rep(i, 1, pn)
{
while (tp&&pre[i].s>=sta[tp].s) tp--;
sta[++tp] = pre[i];
}
rep(i, 1, tp) pre[i] = sta[i];
pn = tp;
sort(suf+1, suf+sn+1);
tp = 0;
rep(i, 1, sn)
{
while (tp&&suf[i].s>=sta[tp].s) tp--;
sta[++tp] = suf[i];
}
rep(i, 1, tp) suf[i] = sta[i];
sn = tp;
}
inline int trans(const Node&p, int x0)
{
return min(p.g, p.s + x0);
}
inline int getopt(Node*q, int x0)
{
int n;
if (q==a) n = sz;
else if (q==pre) n = pn;
else if (q==suf) n = sn;
if (!n) return x0;
int l = 1, r = n, mid, p = -1, ans = x0;
while (l<=r)
{
mid = (l+r)>>1;
if (q[mid].g-q[mid].s >= x0) p = mid, r = mid-1;
else l = mid+1;
}
if (~p)
{
ans = trans(q[p], x0);
if (p<n) gmax(ans, trans(q[p+1], x0));
if (p) gmax(ans, trans(q[p-1], x0));
return max(ans, x0);
}
return max(max(trans(q[1], x0), trans(q[n], x0)), x0);
}
} b[233];
void prep()
{
for (int i = 1; i<=N; i+=bsz)
{
int bid = i/bsz+1;
int lim = (i+bsz>N ? N : i+bsz-1);
b[bid].S = S[lim]-S[i-1];
for (int j = i, cur; j<=lim; ++j)
{
cur = inf, be[j] = i;
for (int k = j; k<=lim; ++k)
{
cur = min(L[k], cur + D[k]);
b[bid].push(Node(cur, S[k] - S[j-1]));
if (j == i) b[bid].pushp(Node(cur, S[k] - S[j-1]));
if (k == lim) b[bid].pushs(Node(cur, S[k] - S[j-1]));
if (j == i && k == lim) b[bid].G = cur;
}
}
b[bid].init();
}
}
void solve(int x, int y, int x0)
{
int ans = x0, i, tmp = x0, lim;
int Y = x0;
for (i = x; i<=y&&i%bsz!=1; ++i)
Y = min(max(Y,x0)+D[i], L[i]), gmax(ans, Y);
gmax(Y, x0);
for (; be[x]!=be[y]&&be[i]<be[y]; i+=bsz)
{
Block &tb = b[i/bsz+1];
gmax(ans, tb.getopt(tb.pre, Y));
lim = min(N, i+bsz-1);
tmp = min(S[lim]-S[i-1]+Y, tb.G);
Y = tmp;
tmp = tb.getopt(tb.a, x0);
gmax(ans, tmp);
tmp = tb.getopt(tb.suf, x0);
gmax(Y, tmp);
if (Y<x0) Y = x0;
}
for (; i<=y; ++i)
Y = min(max(x0,Y)+D[i], L[i]), gmax(ans, Y);
printf("%d\n", ans);
}
int main()
{
scanf("%d%d", &N, &Q);
bsz = sqrt(N+0.5);
rep(i, 1, N) scanf("%d",D+i), S[i]=S[i-1]+D[i];
rep(i, 1, N) scanf("%d",L+i);
prep();
for (int a, b, x0; Q--; )
{
scanf("%d%d%d", &a, &b, &x0);
solve(a, b, x0);
}
return 0;
}