• 洛谷P1295 [TJOI2011]书架 线段树优化dp,单调栈


    P1295 [TJOI2011]书架


    本题思路比较好想(对我来说不是),但代码细节很多,奈何洛谷的题解只有思路,然后就是

    没有丝毫解释的代码,让人看起来很头疼(~~ 尤其是像我这样的蒟蒻~~),所以便打算写一篇带

    注释的题解;

    题目大意

    题目链接

    给出一个长度为 n 的序列 h,请将 h 分成若干段,满足每段数字之和都不超过 m,最小化每段的

    最大值之和。

    解题思路

    我们不难想到30分的n2做法,但期望得分30(据说实际有50)

    我们定义f [ i ]为1 - i分段后的最大值和

    可得dp方程

    [f[i]=f[j]+max(a[j+1]...a[i])(s[i]-s[j]<=m) ]

    我们考虑对其进行优化

    首先我们计算到i时,有(j< k < i(s[i]-s[j]<=m)) 假设a[j] ~a[i] 的最大值与 a[k]~a[i]的最大值相等

    那么 j ,k 实际上对f[i]的值产生的贡献是由f[j] f[k]决定的,那么,又f的单调性 ,即 (f[j]<=f[k] (j<k)),我们知道选择f[j]一定比选择f[k]的值更优秀所以我们只需在和值小于等于m的范围

    内根据到i 的最大值大小分段,使每一段到i的最大值都相等,我们只需计算出每一段中最小下标对应的a加上该段的max,然后在所有段中取得最小,便可以更新出答案将序列分段我们利用单调队列来完

    成,维护没一个值向左的最远影响(实际便是到该点以该点权值为最大值的最小下标) ,利用线段树将每一段的值加载到线段树上,维护合法的最小值,即最终答案即可,时间复杂度为线段树的时间复

    杂度,即(O(nlogn));

    这样我们就可以在规定时间内算出最优解了

    代码部分

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<string>
    
    using namespace std;
    const int maxn=100005;
    //快读
    inline int read(){
       int ret=0;
       int f=1;
       char ch=getchar();
       while(ch<'0'||ch>'9'){
       	if(ch=='-')
       		f=-f;	
       	ch=getchar();
       }
       while(ch<='9'&&ch>='0'){
       	ret=ret*10+(ch^'0');
       	ch=getchar();
       }
       return ret*f;
    }
    // 我们用line来维护单调队列数组中下标在a数组中的数值
    // w为该单调队列下标在a数组中对应的下标
    // v为对应的长度,f 为该值可以影响的最大范围的值的a数组下标
    struct node{
       int w,v,f;
    }lin[maxn];
    int tre[5*maxn];//线段树,实际开4倍就够了
    int n,m;
    int head;
    int tail;
    int a[maxn];
    //将每一段值加载到线段树上
    //为了因为最终答案为最小值,所以我们线段是维护的也是最小值
    void plu(int ro,int l,int r,int v,int nu){
       if(l==r){
       	tre[ro]=v;
       return ;
       }
       int mid=(l+r)>>1;
       if(mid>=nu){
       	plu(ro*2,l,mid,v,nu);
       }
       else{
       	plu(ro*2+1,mid+1,r,v,nu);
       }
       tre[ro]=min(tre[ro*2],tre[ro*2+1]);
       return ;
    }
    //查找最小值
    int sea(int ro,int l,int r,int lf,int rf){
       if(l==r){
       	return tre[ro];
       }
       if(lf<=l&&rf>=r){
       	return tre[ro];
       }
       int mid=(l+r)>>1;
       int ans=0x3f3f3f3f;//理应给最大值,之前给小了,查了半天
       if(lf<=mid){
       	ans=sea(ro*2,l,mid,lf,rf);
       }
       if(rf>=mid+1){
       	ans=min(ans,sea(ro*2+1,mid+1,r,lf,rf));
       }
       return ans;
    }
    int t;
    int h=1;
    int f[maxn];
    int s;
    int last[maxn];//用于存放以该点为结尾<=m的序列的头元素下标 
    int maxx=-1;
    int main(){
       freopen("a.in","r",stdin);
       n=read();
       m=read();
       for(int i=1;i<=n;i++){
       	a[i]=read();
       //	cout<<a[i]<<endl;
       }
       
    //	cout<<n<<" "<<m;
       head=1,tail=0;
       //先进行预处理
       while(t<n&&a[t+1]+s<=m){
       	t++;
       	s+=a[t];
       	last[t]=1;
       	maxx=max(maxx,a[t]);
       	//cout<<t;
       	f[t]=maxx;
       	while(tail>0&&a[lin[tail].w]<=a[t]){
       		tail--;
       	}
       	tail++;
       	lin[tail].w=t;
       	lin[tail].v=a[t];
       	lin[tail].f=lin[tail-1].w;
       //	cout<<lin[tail].f<<endl;
       	plu(1,1,n,f[lin[tail-1].w]+a[t],tail);//将该点以本身为最值的f更新到线段树上
       	//tail++;
       	 
       }
       //维护每个到i的和<=m的最远下标
       for(int i=t+1;i<=n;i++){
       	s+=a[i];
       	while(s>m){
       		s-=a[h];
       		h++; 
       	}
       	last[i]=h; 
       } 
       
       for(int i=t+1;i<=n;i++){
       	while(lin[head].w<last[i]&&head<=tail){
       		lin[head].w=0;
       		head++;
       	}//保证head在合法范围内即不能大于m 
       	if(lin[head].f<last[i]-1){
       		lin[head].f=last[i]-1;
       		plu(1,1,n,f[last[i]-1]+lin[head].v,head);//将更新后的head加载到线段树上至于为什么要更新,因为head下的值已经发生变化 
       	}
       	while(head<=tail&&a[i]>=lin[tail].v){
       		tail--;
       	}
       	tail++;
       	lin[tail].w=i;
       	lin[tail].v=a[i];
       	lin[tail].f=max(lin[tail-1].w,last[i]-1);
       	plu(1,1,n,f[lin[tail].f]+lin[tail].v,tail);//加载然后搜索即可
       	f[i]=sea(1,1,n,head,tail);//
       	//tail++;
       }
       cout<<f[n];
       return 0;
    } 
    

    完结撒花
    以小紬结尾

  • 相关阅读:
    BZOJ2435 NOI2011道路修建
    BZOJ2431 HAOI2009逆序对数列(动态规划)
    BZOJ2456 mode
    BZOJ2324 ZJOI2011营救皮卡丘(floyd+上下界费用流)
    BZOJ2303 APIO2011方格染色(并查集)
    BZOJ2299 HAOI2011向量(数论)
    BZOJ2169 连边(动态规划)
    BZOJ2159 Crash的文明世界(树形dp+斯特林数)
    洛谷 P1306 斐波那契公约数 解题报告
    洛谷 P2389 电脑班的裁员 解题报告
  • 原文地址:https://www.cnblogs.com/rpup/p/13684512.html
Copyright © 2020-2023  润新知