[BZOJ2122]工作评估

Description

给出一个长度为n的序列,第i天有状态值Di和评估上限Li
若当前的评估值为x0,第i天后会变成min(x0+Di,Li)
需要回答q次询问,第i次询问给出L,R,x0,求[L,R]的一个子区间[l,r]使得经过这些天之后最后的评估值最大。
n,q<=5e4

Solution

设f(l,r,x)表示若当前评估值为x,经过[l,r]这个区间会变成什么,f有两个性质
1.若a>=b,f(l,r,a)>=f(l,r,b)
2.令g(l,r)=f(l,r,inf),即在第一个位置被卡住
则f(l,r,x)=min(g(l,r),s(l,r)+x),s(l,r)表示区间[l,r]中Di的和
感受一下若被卡住一定是g(l,r),否则是s(l,r)+x,判断是否被卡住直接取min即可
然后考虑分块,注意到如果区间[l1,r1]和[l2,r2]满足g(l1,r1)>g(l2,r2)且s(l1,r1)>s(l2,r2)那么[l2,r2]肯定是没有用的
我们可以对每一块求出所有子区间的g(l,r)和s(l,r)然后将没有用的删去
那么我们得到了一个g上升s下降的序列,需要求的就是 max ⁡ l , r ( m i n ( g ( l , r ) , s ( l , r ) + x ) ) \max_{l,r}(min(g(l,r),s(l,r)+x)) l,rmax(min(g(l,r),s(l,r)+x))
直接二分找交点判断左右两边即可
那么我们解决了在块内的情况,对于跨过块的我们可以动态维护一个y表示前i块最大能传过来的数,因为y越大f越大
那么跨块的部分直接维护一个前缀的S和G
更新y只有两种状态,一种是从前面继承过来,一种是从这个块某个位置开始走
后一种只需要再对后缀维护S和G就能更新了
复杂度 O ( n n l o g n ) O(n\sqrt n log n) O(nn logn)
如果排序写桶排可以平衡规划 O ( n n l o g n ) O(n\sqrt {nlogn}) O(nnlogn )

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;

int read() {
	char ch;int sig=1;
	for(ch=getchar();ch<'0'||ch>'9';ch=getchar()) if (ch=='-') sig=-1;
	int x=ch-'0';
	for(ch=getchar();ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*sig;
}

void write(int x) {
	if (!x) {puts("0");return;}
	char ch[20];int tot=0;
	for(;x;x/=10) ch[++tot]=x%10+'0';
	fd(i,tot,1) putchar(ch[i]);
	puts("");
}

const int N=4e4+5,M=205;

int n,q,pos[N],lim[N],d[N],sum[N],f[M][M][M],L[M],R[M],stk[N],tot,top;

int G(int l,int r) {
	int u=pos[l];
	return f[u][l-L[u]][r-L[u]];
}

int S(int l,int r) {return sum[r]-sum[l-1];}

int F(int l,int r,int x) {return min(G(l,r),S(l,r)+x);}

struct P{
	int g,s;
	P(int _g=0,int _s=0) {g=_g;s=_s;}
}p[N],pr[M][M],su[M][M],t[M][N];
bool cmp(P a,P b) {return a.g<b.g||a.g==b.g&&a.s<b.s;}

void get(P *pt) {
	sort(p+1,p+tot+1,cmp);
	top=0;
	fo(i,1,tot) {
		while (top&&p[i].s>=p[stk[top]].s) top--;
		stk[++top]=i;
	}
	pt[0].s=top;
	fo(i,1,top) pt[i]=p[stk[i]];
}

void pre(int x) {
	fo(i,L[x],R[x]) {
		f[x][i-L[x]][i-L[x]]=lim[i];
		fo(j,i+1,R[x]) f[x][i-L[x]][j-L[x]]=min(f[x][i-L[x]][j-L[x]-1]+d[j],lim[j]);
	}
	tot=0;fo(i,L[x],R[x]) fo(j,i,R[x]) p[++tot]=P(G(i,j),S(i,j));get(t[x]);
	tot=0;fo(i,L[x],R[x]) p[++tot]=P(G(L[x],i),S(L[x],i));get(pr[x]);
	tot=0;fo(i,L[x],R[x]) p[++tot]=P(G(i,R[x]),S(i,R[x]));get(su[x]);
}

int calc(P *pt,int x) {
	int l=1,r=pt[0].s;
	while (l<r) {
		int mid=l+r>>1;
		if (pt[mid].g<=pt[mid].s+x) l=mid+1;
		else r=mid;
	}
	int res=min(pt[l].g,pt[l].s+x);
	res=max(res,min(pt[l-1].g,pt[l-1].s+x));
	return res;
}

int go(int l,int r,int &x,int x0) {
	int res=x0;
	fo(i,l,r) {
		x=min(max(x,x0)+d[i],lim[i]);
		res=max(res,x);
	}
	return res;
}

int solve(int l,int r,int x) {
	int u=pos[l],v=pos[r];
	if (u==v) return go(l,r,x,x);
	int y=x,ans=go(l,R[u],y,x);y=max(y,x);
	fo(i,u+1,v-1) {
		ans=max(ans,calc(t[i],x));
		ans=max(ans,calc(pr[i],y));
		y=F(L[i],R[i],y);
		y=max(y,calc(su[i],x));
	}
	return max(ans,go(L[v],r,y,x));
}

int main() {
	freopen("park.in","r",stdin);
	freopen("park.out","w",stdout);
	n=read();q=read();int m=200;
	fo(i,1,n) d[i]=read(),sum[i]=sum[i-1]+d[i];
	fo(i,1,n) lim[i]=read();
	int sz=n/m+((n%m)>0);
	fo(i,1,sz) {
		L[i]=R[i-1]+1;
		R[i]=min(L[i]+m-1,n);
		fo(j,L[i],R[i]) pos[j]=i;
	}
	fo(i,1,sz) pre(i);
	for(;q;q--) {
		int l=read(),r=read(),x=read();
		write(solve(l,r,x));
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值