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);
}