把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

luogu P6240 好吃的题目

题面传送门
一句话题面:区间01背包。
这种东西可以用分治解决掉。
发现如果完全合并两个dp状态是\(O(t^2)\)的,但是如果只是针对某一个值的合并只是\(O(t)\)的。所以这题中不能进行合并状态。
预处理线段树上每个节点\(l\)\(mid\)\(mid+1\)\(r\)的两部分\(dp\)状态。
在一棵线段树上,将所有询问推到不能再推时,即如果再推会分成两个询问时,合并两边的\(dp\)状态来得出答案。
时间复杂度\(O(ntlogn+mt)\)
代码实现:

#include<cstdio>
#include<cstring>
#define max(a,b) ((a)>(b)?(a):(b))
using namespace std;
int n,m,k,x,y,z,dp[40039][239],a[40039],b[40039],mid,maxt,flag;
struct yyy{int x,y,l,r,z,flag,ans;}f[200039];
int main(){
//	freopen("1.in","r",stdin);
	register int i,j,h;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++)scanf("%d",&a[i]);
	for(i=1;i<=n;i++) scanf("%d",&b[i]);
	for(i=1;i<=m;i++) scanf("%d%d%d",&f[i].x,&f[i].y,&f[i].z),f[i].l=1,f[i].r=n,maxt=max(maxt,f[i].z);
	while(1){
		memset(dp,-0x3f,sizeof(dp));
		for(i=1;i<=m;i++){
			if(f[i].flag) continue; 
			mid=f[i].l+f[i].r>>1;
			if(dp[mid][0]<=-1e9){
				for(j=0;j<a[mid];j++) dp[mid][j]=0;
				for(j=a[mid];j<=maxt;j++) dp[mid][j]=b[mid];
				if(f[i].l<=mid-1){
					for(j=mid-1;j>=f[i].l;j--){
				    	for(h=0;h<=maxt;h++){
				    		dp[j][h]=dp[j+1][h];
				    		if(h>=a[j]) dp[j][h]=max(dp[j][h],dp[j+1][h-a[j]]+b[j]);
				    	}
				    } 
				}
				if(mid+1<=f[i].r){
					for(j=0;j<a[mid+1];j++) dp[mid+1][j]=0;
				    for(j=a[mid+1];j<=maxt;j++) dp[mid+1][j]=b[mid+1];
				    for(j=mid+2;j<=f[i].r;j++){
				    	for(h=0;h<=maxt;h++){
				    		dp[j][h]=dp[j-1][h];
				    		if(h>=a[j]) dp[j][h]=max(dp[j][h],dp[j-1][h-a[j]]+b[j]);
				    	}
				    } 
				}
			}
		} flag=0;
		for(i=1;i<=m;i++){
			if(f[i].flag) continue;
			flag=1;mid=f[i].l+f[i].r>>1;
			if(f[i].l==f[i].r){f[i].ans=dp[mid][f[i].z];f[i].flag=1;continue;}
			if(f[i].y<=mid) f[i].r=mid;
			else if(f[i].x>mid) f[i].l=mid+1;
			else{
				for(j=0;j<=f[i].z;j++) f[i].ans=max(f[i].ans,dp[f[i].x][j]+dp[f[i].y][f[i].z-j]);
				f[i].flag=1;
			}
		}
		if(!flag) break;
	}
	for(i=1;i<=m;i++) printf("%d\n",f[i].ans);
}
posted @ 2021-01-02 18:24  275307894a  阅读(92)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end