zoukankan      html  css  js  c++  java
  • 单调队列优化DP

    全局最优解必然包含局部最优解,因此每次转移只需考虑局部最优解!!!

    主要内容

    形如这样(operatorname{DP}) 转移方程:

    [dp[i]=max_{L_ile jle R_i}{{dp[i]+val(i,j)}} ]

    满足:

    1. ({L_i}) , ({R_i}) 递增( 前提条件 )。

    2. (R_i le i) ( 转移条件 )。

    3. (val(i,j)) 值只与 (j) 相关 ( 根本优化转移前提 ) 。

    维护一个滑动窗口,每次求窗口中的最大值。对于两个点 (x)(y) ,如果 (x < y)(f(x) < f(y)) ,那么 (y) 进入窗口后,决策点一定不会是 (x)

    用一个单调队列维护窗口里所有可能用到的决策点。

    窗口右端点向右滑动时,把一个新的点插入队尾。队尾点为 (q[r]) ,新点为 (x) ,如果 (f(q[r]) le f(x)) ,那么 (q[r]) 没用,把 (q[r]) 弹掉。重复过程直到队尾点可能有用,即 (f(q[r]) > f(x)) ,把 (x) 入队。

    队列中的 (f(i)) 从队首到队尾递减。决策时,首先弹掉队首超过范围的点。这时队首点就是决策点。(f(i) = max {{f(j) + w_i}} [i-R_i le j le i-L_i])

    单调队列优化 也称为 滑动窗口


    变式 (-) 单调队列优化多重背包

    内容

    (dp[i][j]) 表示前 (i) 个物品放入容量为 (j) 的背包的最大收益 。

    [dp[i][j]=max_{k=0}^{kle k[i]}{{dp[i-1][j-k imes c[i]]+k imes w[i]}} ]

    考虑 (dp) 的转移 。

    [0le p < c[i],0le j le leftlfloor dfrac{V-p}{c[i]} ight floor,0le k le k[i] ]

    [dp[i][p+j imes c]=max{{dp[i-1][p+(j-k) imes c]+k imes w}} ]

    [dp[i][p+j imes c]=max{{dp[i-1][p+(j-k) imes c]-(j-k) imes w+j imes w}} ]

    [dp[i][p+j imes c]=max{{dp[i-1][p+(j-k) imes c]-(j-k) imes w}}+j imes w ]

    这样就可以进行单调队列优化了 。

    时间复杂度:(O(nV))

    核心代码:( P1776 宝物筛选 ) 代码中的 (pos) 就是上面的 (j-k)

    int ql,qr;
    struct QUE
    {
    	 int num,val;
    }que[Maxv];
    void many_pack(int c,int w,int m)
    {
    	 if(!c) { add+=m*w; return; }
    	 m=min(m,V/c);
    	 for(int pos=0,s;pos<c;pos++)
    	 {
    	 	 ql=1,qr=0,s=(V-pos)/c;
    	 	 for(int j=0;j<=s;j++)
    	 	 {
    	 	 	 while(ql<=qr && que[qr].val<=(dp[pos+j*c]-j*w)) qr--;
    	 	 	 que[++qr]=(QUE){j,dp[pos+j*c]-j*w};
    	 	 	 while(ql<=qr && (j-que[ql].num)>m) ql++;
    	 	 	 dp[pos+j*c]=max(dp[pos+j*c],que[ql].val+j*w);
    		 }
    	 }
    }
    

    多重背包的其他解法:二进制分组优化 ,时间复杂度: (O(Vsum_{i=1}^{n}log_2{k_i})) ,见背包问题

    注意:

    用结构体存储单调队列,防止反复修改 (dp) 值。

    并注意 (j=0) 时的情况,及时更新。


    二维单调队列

    对于每一列维护一个竖直方向上的一维单调队列。

    在每一行统计答案的时候,用一个新的一维单调队列维护每一列的最优答案。

    最终答案在新的一维单调队列上。

    例题:P2219 [HAOI2007]修筑绿化带


    例题

    P1725 琪露诺

    $ exttt{solution}$

    状态:设 (dp[i]) 表示走到 (i) 的最大收益。

    (L)(R) 都是上文中的转移范围。

    核心代码:

    n=rd(),tmpl=rd(),tmpr=rd();
    for(int i=0;i<=n;i++) a[i]=rd();
    for(int i=1;i<=n;i++) L[i]=i-tmpr,R[i]=i-tmpl;
    memset(dp,-inf,sizeof(dp));
    dp[0]=a[0];
    for(int i=1;i<=n;i++) // 必须从 l 开始 
    {
    	 if(R[i]<0) continue;
    	 while(l<=r && q[l]<L[i]) l++;
    	 while(l<=r && dp[q[r]]<=dp[R[i]]) r--;
    	 q[++r]=R[i]; // 因为 i-L 小于 i ,所以应该确保最有决策再进行转移 
    	 dp[i]=dp[q[l]]+a[i];
    }
    int ans=-inf;
    for(int i=L[n]+1;i<=n;i++) ans=max(ans,dp[i]);
    printf("%d
    ",ans);
    

    P3572 [POI2014]PTA-Little Bird

    $ exttt{solution}$

    状态:(dp[i]) 表示到 (i) 为止的最小代价。

    核心代码:

    bool Better(int x,int y)
    {
    	 if((dp[x]<dp[y]) || (dp[x]==dp[y] && h[x]>=h[y])) return true;
    	 return false;
    }
    for(int i=1;i<=n;i++) L[i]=i-k,R[i]=i-1;
    q[1]=l=r=1;
    for(int i=2;i<=n;i++)
    {
    	 if(R[i]<0) continue;
    	 while(l<=r && q[l]<L[i]) l++;
    	 while(l<=r && Better(R[i],q[r])) r--;
    	 q[++r]=R[i];
    	 dp[i]=dp[q[l]]+(h[q[l]]<=h[i]);
    }
    printf("%d
    ",dp[n]);
    

    P3957 跳房子

    $ exttt{solution}$

    注意点:

    1. 存储要开 ( exttt{long}~ exttt{long})

    2. 注意转移的左右端点判断问题。

    代码:

    bool check(int g)
    {
    	 memset(dp,-inf,sizeof(dp)),dp[0]=0;
    	 memset(L,inf,sizeof(L)),memset(R,-1,sizeof(R));
    	 int tl=max(1,d-g),tr=d+g;
    	 ll MAX=-inf;
    	 for(int i=0,tmpr=0;i<=n;i++) // 判断左端点 
    	 	 while(tmpr<=n && pos[i]+tr>=pos[tmpr]) L[tmpr++]=i;
    	 for(int i=n,tmpl=n;i>=0;i--) // 判断右端点 
    	 {
    	 	 while(pos[i]+tr<pos[tmpl]) tmpl--; // 可能没有右端点 
    	 	 while(tmpl>=0 && pos[i]+tl<=pos[tmpl] && pos[i]+tr>=pos[tmpl]) R[tmpl--]=i;
    	 }
    	 for(int i=1,l=1,r=0;i<=n;i++)
    	 {
    	 	 if(L[i]>R[i]) continue;
    	 	 while(l<=r && q[l]<L[i]) l++;
    	 	 for(int j=max(R[i-1]+1,L[i]);j<=R[i];j++) // 右端点不一定是连续的,而且可能是 -1 
    	 	 {
    		 	 while(l<=r && dp[q[r]]<dp[j]) r--;
    		 	 q[++r]=j;
    		 }
    	 	 if(l<=r) dp[i]=dp[q[l]]+val[i];
    	 }
    	 for(int i=0;i<=n;i++) MAX=maxll(MAX,dp[i]);
    	 return (MAX>=k);
    }
    
    int l=1,r=pos[n];
    while(l<=r)
    {
    	 int mid=(l+r)/2;
    	 if(check(mid)) ans=mid,r=mid-1;
    	 else l=mid+1;
    }
    printf("%d
    ",ans);
    

    P1099 树网的核 (&) P2491 [SDOI2011]消防(加强版 树网的核)

    $ exttt{solution}$
    1. 求直径( 这应该都会 ) 。

    2. 在直径上单调队列,求出不大于 (s) 的一个区间,使区间两端到直径两端的最大值最小 ( 即那一段区间的 (operatorname{GCC})

    3. 对每一个直径上的点求:到有这个点伸出去的最远距离,与答案取 (max)

    (Finish)

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    #define infll 0x3f3f3f3f3f3f3f3f
    #define inf 0x3f3f3f3f
    #define Maxn 500005
    typedef long long ll;
    inline int rd()
    {
    	 int x=0;
         char ch,t=0;
         while(!isdigit(ch = getchar())) t|=ch=='-';
         while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();
         return x=t?-x:x;
    }
    int n,s,tot,root1,root2,cnt,tmp,ans=inf;
    int d[Maxn],fa[Maxn],que[Maxn],mx[Maxn],addmx[Maxn];
    int hea[Maxn],nex[Maxn*2],ver[Maxn*2],edg[Maxn*2];
    bool isdia[Maxn];
    void add(int x,int y,int d)
    {
    	 ver[++tot]=y,edg[tot]=d,nex[tot]=hea[x],hea[x]=tot;
    }
    void dfs(int x)
    {
    	 for(int i=hea[x];i;i=nex[i])
    	 {
    	 	 if(ver[i]==fa[x]) continue;
    	 	 fa[ver[i]]=x;
    	 	 d[ver[i]]=d[x]+edg[i];
    		 dfs(ver[i]);
    	 }
    }
    void dfs2(int x,int F,int dis)
    {
    	 ans=max(ans,dis);
    	 for(int i=hea[x];i;i=nex[i])
    	 {
    	 	 if(ver[i]==F || isdia[ver[i]]) continue;
    	 	 dfs2(ver[i],x,dis+edg[i]);
    	 }
    }
    int main()
    {
         //freopen(".in","r",stdin);
         //freopen(".out","w",stdout);
    	 n=rd(),s=rd();
    	 int u,v,eg;
    	 for(int i=1;i<n;i++)
    	 {
    	 	 u=rd(),v=rd(),eg=rd();
    	 	 add(u,v,eg),add(v,u,eg);
    	 }
    	 dfs(1);
    	 for(int i=1;i<=n;i++) if(d[i]>d[root1]) root1=i;
    	 fa[root1]=0,d[root1]=0;
    	 dfs(root1);
    	 for(int i=1;i<=n;i++) if(d[i]>d[root2]) root2=i;
    	 for(tmp=root2;tmp!=root1;tmp=fa[tmp]) mx[++cnt]=d[tmp],isdia[tmp]=true;
    	 mx[++cnt]=0,isdia[root1]=true;
    	 for(int i=1,l=1,r=0;i<=cnt;i++)
    	 {
    	 	 while(l<=r && mx[que[l]]>mx[i]+s) l++;
    	 	 que[++r]=i;
    	 	 ans=min(ans,max(mx[1]-mx[que[l]],mx[que[r]]));
    	 }
    	 for(int i=1;i<=n;i++) if(isdia[i]) dfs2(i,0,0);
    	 printf("%d
    ",ans);
         //fclose(stdin);
         //fclose(stdout);
         return 0;
    }
    

    CF372C Watching Fireworks is Fun

    $ exttt{solution}$

    状态:设 (dp[i][j]) 表示在放第 (i) 个烟花的时候,在第 (j) 个位置的最大快乐值 。

    转移方程:

    [j-(t[i]-t[i-1]) imes dle kle j+(t[i]-t[i-1]) imes d ]

    [dp[i][j]=max{{dp[i-1][k]+b[i]-operatorname{abs}(a[i]-j)}} ]

    将转移方程变形:

    [dp[i][j]=b[i]-operatorname{abs}(a[i]-j)+max{{dp[i-1][k]}} ]

    一看到上面这个方程,一眼就会想到 单调队列优化(operatorname{DP})

    可以用一个单调队列维护 (dp[i-1][j-(t[i]-t[i-1]),j+(t[i]-t[i-1])]) 的最大值,转移到 (dp[i][j]) ,转移均摊 (O(1))

    复杂度:(O(nm))

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    #define infll 0x7f7f7f7f7f7f7f7f
    #define inf 0x7f7f7f7f
    #define Maxn 150005
    #define Maxm 305 
    typedef long long ll;
    inline ll rd()
    {
    	 ll x=0;
         char ch,t=0;
         while(!isdigit(ch = getchar())) t|=ch=='-';
         while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();
         return x=t?-x:x;
    }
    ll maxll(ll x,ll y){ return x>y?x:y; }
    ll minll(ll x,ll y){ return x<y?x:y; }
    ll absll(ll x){ return x>0ll?x:-x; }
    ll n,m,d,l,r,pos,que[Maxn];
    ll p,ans=-infll,a[Maxm],b[Maxm],t[Maxm],f[Maxn],g[Maxn];
    int main()
    {
         //freopen(".in","r",stdin);
         //freopen(".out","w",stdout);
    	 n=rd(),m=rd(),d=rd();
    	 for(ll i=1;i<=m;i++) a[i]=rd(),b[i]=rd(),t[i]=rd();
    	 for(ll i=1;i<=m;i++)
    	 {
    	 	 l=1,r=0,pos=1,p=(t[i]-t[i-1])*d;
    	 	 for(ll j=1;j<=n;j++)
    	 	 {
    	 	 	 for(;pos<=minll(n,j+p);pos++)
    	 	 	 {
    	 	 	 	 while(l<=r && g[que[r]]<=g[pos]) r--;
    	 	 	 	 que[++r]=pos;
    			 }
    			 while(l<=r && que[l]<j-p) l++;
    			 f[j]=g[que[l]]+b[i]-absll(a[i]-j);
    		 }
    		 memcpy(g,f,sizeof(f));
    	 }
    	 for(ll i=1;i<=n;i++) ans=maxll(ans,f[i]);
    	 printf("%lld
    ",ans);
         //fclose(stdin);
         //fclose(stdout);
         return 0;
    }
    

    烧桥计划

    $ exttt{solution}$

    题意:

    给你长为 (n) 的序列 (a_1,a_2,cdots,a_n) 和一个参数 (m)

    删掉其中若干个位置 (p_1,p_2,cdots,p_k) ,耗费 (sum_{i=1}^{k}icdot a_{p_i}) 的代价,将序列分为 (k) 段。

    再对每段求和,若 (sum_k>m) ,则需额外支付 (sum_k) 的代价。求最小总代价。

    (nleq 10^5,1000leq a_i leq 2000)

    题解:

    (step~1)

    列出基础转移方程 。

    状态:设 (dp[i][j]) 表示前 (i) 个位置中选择删去了 (j) 个点,其中第 (i) 个点强制删去的最小代价。可以通过在序列最后增加一个 (0) 使 (min{{dp[n+1][i]}}) 变为答案 。

    转移:( (sum) 表示前缀和 )

    [dp[i][j]=min{{dp[k-1][j]+calc(i-1,k)}}+j imes a[i] ]

    (calc(i,j)) 表示从 (j+1)(i) 的代价 。

    [calc(i,j)=[sum[i]-sum[j]>m] imes (sum[i]-sum[j]) ]

    通过枚举 (i~j~k) 复杂度为 (O(n^3))

    (step~2)

    观察数据范围:如果一个点都不删,最大代价为 (2000 imes n) ;如果删除 (k) 个点,最小代价为 (1000 imes dfrac{k(k+1)}{2})

    因此 ( exttt{选择删除的点数}le 2sqrt{n})(jle 2sqrt{n}) ) 。

    (step~3)

    将数组滚动,省去 (j) 那一维,转移最多进行 (2sqrt{n}) 次 。

    转移可以由两部分组成:

    • (calc(i-1,k)>0) :即 (calc(i-1,k)=sum[i-1]-sum[k])

      (dp[i][opt]=min{{dp[k][optoplus1]-sum[k]}}+s[i-1])

      可以在每一次 (i) 增加的时候更新 (k) 值,并在更新的时候记录最小的 (dp[k][optoplus1]-sum[k]) ( 记为 (tmp1) )。

    • (calc(i-1,k)=0)

      (dp[i][opt]=min{{dp[k][optoplus1]}})

      可以用单调队列来维护这个最小值( 记为 (tmp2) ) 。

    最终的 (dp[i][opt]=min{(tmp1+sum[i-1],tmp2)}+j imes a[i])

    时间复杂度:(O(nsqrt{n}))

    核心代码:

    struct Data{ ll val,num; }q[Maxn];
    
    n=rd(),m=rd(),lim=2*(sqrt(n)+1);
    for(ll i=1;i<=n;i++) a[i]=rd(),sum[i]=sum[i-1]+a[i];
    a[++n]=0,sum[n]=sum[n-1];
    memset(dp,inf,sizeof(dp)),dp[0][0]=dp[0][1]=0;
    for(ll i=1;i<=n;i++) dp[i][opt]=sum[i-1]>m?sum[i]:a[i];
    ans=dp[n][opt];
    for(ll k=2;k<=lim;k++)
    {
    	 opt^=1;
    	 ll l=1,r=0,id=0,tmp=infll;
    	 for(ll i=1;i<=n;i++)
    	 {
    	 	 while(sum[i-1]-sum[id]>m) tmp=minll(tmp,dp[id][opt^1]-sum[id++]);
    	 	 while(l<=r && q[l].num<id) l++;
    	 	 dp[i][opt]=minll(tmp+sum[i-1],q[l].val)+k*a[i];
    	 	 while(l<=r && q[r].val>dp[i][opt^1]) r--;
    	 	 q[++r]=(Data){dp[i][opt^1],i};
    	 }
    	 ans=min(ans,dp[n][opt]);
    }
    printf("%lld
    ",ans);
    

    P2254 [NOI2005]瑰丽华尔兹

    $ exttt{solution}$

    (step~1)

    考虑首先考虑对于时间 (t)(dp)

    状态:设 (dp[t][i][j]) 表示在第 (t) 时刻在第 (i) 行第 (j) 列所能获得的最长距离 。

    转移:( (pre_i)(pre_j) 为上一个合法的位置)

    [dp[t][i][j]=max{{dp[t-1][i][j],dp[t-1][pre_i][pre_j]+1}} ]

    时间复杂度:(O(TN^2))

    核心代码:

    for(int i=1;i<=k;i++) l[i]=rd(),r[i]=rd(),d[i]=rd()-1;
    memset(dp,-inf,sizeof(dp)),dp[0][sx][sy]=0;
    for(int t=1,p=1;t<=r[k];t++)
    {
    	 if(t>r[p]) p++;
    	 for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(mp[i][j]=='.')
    	 {
    	 	 int lx=i-zou[d[p]][0],ly=j-zou[d[p]][1];
    	  	 dp[t][i][j]=max(dp[t-1][i][j],dp[t-1][lx][ly]+1);
    	 }
    }
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) ans=max(ans,dp[r[k]][i][j]);
    printf("%d
    ",ans);
    

    (step~2)

    把时间 (t) 换成区间 (k)

    状态:(dp[k][i][j]) 表示在第 (k) 段滑行区间中在位置 (i)(j) 所能获得最长距离 。

    注意到在第 (k) 段时间内只能向某个方向最多走 (x) 步( (x) 为区间长度 ),得到转移方程 :

    [dp[k][i][j]=max{{dp[k-1][i][j],dp[k][pre_i][pre_j]+dis(i,j,pre_i,pre_j)}} ]

    时间复杂度:(O(KN^3))

    核心代码:

    for(int i=1;i<=k;i++) l[i]=rd(),r[i]=rd(),d[i]=rd()-1;
    memset(dp,-inf,sizeof(dp)),dp[0][sx][sy]=0;
    for(int p=1;p<=k;p++)
    {
    	 int addx=zou[d[p]][0],addy=zou[d[p]][1];
    	 for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(mp[i][j]=='.')
    	 {
    	 	 for(int lx=i,ly=j,add=0;mp[lx][ly]=='.' && add<=r[p]-l[p]+1;lx-=addx,ly-=addy,add++)
    	  	 	 dp[p][i][j]=max(dp[p][i][j],max(dp[p-1][i][j],dp[p-1][lx][ly]+add));
    	 }
    }
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) ans=max(ans,dp[k][i][j]);
    printf("%d
    ",ans);
    

    (step~3)

    加上 单调队列滚动数组 优化 。

    怎样单调队列:对于第 (i) 段区间钢琴移动的方向,即:行(列),在每一列(行)都做一遍单调队列,相当于快速求出了在第 (i) 段区间结束后的最优值 。

    用单调队列求最优值的时候,可以用偏移量( (pos) )来快速计算两个点之间的距离,并且在遇到障碍块的时候清空队列 。

    时间复杂度:(O(TN^2)) ,空间复杂度:(O(N^2))

    核心代码:

    struct que{ int val,pos; }q[Maxn];
    bool ok(int x,int y)
    {
    	 if(x<1 || x>n || y<1 || y>m) return false;
    	 return true;
    }
    void solve(int sx,int sy,int len,int addx,int addy)
    {
    	 int l=1,r=0;
    	 for(int tx=sx,ty=sy,cnt=1;ok(tx,ty);tx+=addx,ty+=addy,cnt++)
    	 {
    	 	 if(mp[tx][ty]=='x') l=1,r=0;
    	 	 else
    	 	 {
    	 	 	 while(l<=r && q[r].val+cnt-q[r].pos<dp[tx][ty]) r--;
    	 	 	 q[++r]=(que){dp[tx][ty],cnt};
    	 	 	 while(l<=r && cnt-q[l].pos>len) l++;
    	 	 	 dp[tx][ty]=max(dp[tx][ty],q[l].val+cnt-q[l].pos);
    		 }
    	 }
    }
    
    memset(dp,-inf,sizeof(dp)),dp[sx][sy]=0;
    for(int p=1;p<=k;p++)
    {
    	 int l=rd(),r=rd(),d=rd(),len=r-l+1;
    	 if(d==1) for(int i=1;i<=m;i++) solve(n,i,len,-1,0);
    	 if(d==2) for(int i=1;i<=m;i++) solve(1,i,len,1,0);
    	 if(d==3) for(int i=1;i<=n;i++) solve(i,m,len,0,-1);
    	 if(d==4) for(int i=1;i<=n;i++) solve(i,1,len,0,1);
    }
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) ans=max(ans,dp[i][j]);
    printf("%d
    ",ans);
    

    P2569 [SCOI2010]股票交易

    $ exttt{solution}$

    (step~1)

    先列出基础的转移方程,比较容易想到。

    状态:设 (dp[i][j]) 表示,到第 (i) 天,拥有 (j) 分股票的最大收益(可能为负)。

    转移:暴力枚举 (pre_i,pre_j) ,进行转移。

    核心代码:

    t=rd(),mp=rd(),w=rd();
    for(int i=1;i<=t;i++) pa[i]=rd(),pb[i]=rd(),sa[i]=rd(),sb[i]=rd();
    memset(dp,-inf,sizeof(dp)),dp[0][0]=0;
    for(int i=1;i<=t;i++) for(int j=0;j<=mp;j++) for(int pi=0;pi<=i-w-1;pi++)
     	 {
     	 	 for(int pj=max(j-sa[i],0);pj<j;pj++)
     	 	 	 dp[i][j]=max(dp[i][j],dp[pi][pj]-pa[i]*(j-pj));
     	 	 for(int pj=j+1;pj<=min(j+sb[i],mp);pj++)
     	 	 	 dp[i][j]=max(dp[i][j],dp[pi][pj]+pb[i]*(pj-j));
    	 }
    for(int i=0;i<=t;i++) for(int j=0;j<=mp;j++) ans=max(ans,dp[i][j]);
    printf("%d
    ",ans);
    

    复杂度:(O(n^2Maxp^2))

    (step~2)

    可以发现 (dp[i][j]) 只会从 (dp[i-w-1][dots]) 转移过来,因为 (dp[i-w-1][dots]) 已经是当时的最优决策。

    注意进行一系列初始化:

    • (合法情况下) (dp[i][j]=-pa[i] imes j)

    • (dp[i][j]=max{{dp[i][j],dp[i-1][j]}}) (不转移) 。

    • (dp[i][j]=max{{dp[i][j],dp[i-w-1][dots]+dots}}) (进行交易)。

    核心代码:

    t=rd(),mp=rd(),w=rd();
    for(int i=1;i<=t;i++) pa[i]=rd(),pb[i]=rd(),sa[i]=rd(),sb[i]=rd();
    memset(dp,-inf,sizeof(dp)),dp[0][0]=0;
    for(int i=1;i<=t;i++) for(int j=0;j<=sa[i];j++) dp[i][j]=-pa[i]*j;
    for(int i=1;i<=t;i++) for(int j=0;j<=mp;j++)
    	 {
    	 	 dp[i][j]=max(dp[i][j],dp[i-1][j]);
    	 	 int pi=i-w-1;
    	 	 if(pi<0) continue;
    	 	 for(int pj=max(j-sa[i],0);pj<j;pj++)
    	 	 	 dp[i][j]=max(dp[i][j],dp[pi][pj]-pa[i]*(j-pj));
    	 	 for(int pj=j+1;pj<=min(j+sb[i],mp);pj++)
    	 	 	 dp[i][j]=max(dp[i][j],dp[pi][pj]+pb[i]*(pj-j));
    	 }
    for(int i=0;i<=t;i++) for(int j=0;j<=mp;j++) ans=max(ans,dp[i][j]);
    printf("%d
    ",ans);
    

    复杂度: (O(nMaxp^2))

    (step~3)

    发现在

    for(int pj=max(j-sa[i],0);pj<j;pj++)
     	 dp[i][j]=max(dp[i][j],dp[pi][pj]-pa[i]*(j-pj));
    for(int pj=j+1;pj<=min(j+sb[i],mp);pj++)
     	 dp[i][j]=max(dp[i][j],dp[pi][pj]+pb[i]*(pj-j));
    

    中,可以用单调队列进行优化,然后就做完了 。

    核心代码:

    t=rd(),mp=rd(),w=rd();
    for(int i=1;i<=t;i++) pa[i]=rd(),pb[i]=rd(),sa[i]=rd(),sb[i]=rd();
    memset(dp,-inf,sizeof(dp)),dp[0][0]=0;
    for(int i=1;i<=t;i++) for(int j=0;j<=sa[i];j++) dp[i][j]=-pa[i]*j;
    for(int i=1;i<=t;i++)
    {
    	 int pi=i-w-1;
    	 for(int j=0;j<=mp;j++) dp[i][j]=max(dp[i][j],dp[i-1][j]);
    	 if(pi<0) continue;
    	 for(int j=1,l=1,r=0;j<=mp;j++)
    	 {
    	 	 while(l<=r && q[l]<j-sa[i]) l++;
    	 	 while(l<=r && dp[pi][q[r]]+pa[i]*q[r]<=dp[pi][j-1]+pa[i]*(j-1)) r--;
    	 	 q[++r]=j-1;
    	 	 dp[i][j]=max(dp[i][j],dp[pi][q[l]]+pa[i]*q[l]-pa[i]*j);
    	 }
    	 for(int j=mp-1,l=1,r=0;j>=0;j--)
    	 {
    	 	 while(l<=r && q[l]>j+sb[i]) l++;
    	 	 while(l<=r && dp[pi][q[r]]+pb[i]*q[r]<=dp[pi][j+1]+pb[i]*(j+1)) r--;
    	 	 q[++r]=j+1;
    	 	 dp[i][j]=max(dp[i][j],dp[pi][q[l]]+pb[i]*q[l]-pb[i]*j);
    	 }
    }
    for(int i=0;i<=t;i++) for(int j=0;j<=mp;j++) ans=max(ans,dp[i][j]);
    printf("%d
    ",ans);
    

    复杂度: (O(nMaxp)) ,可以通过此题 。

    P2300 合并神犇

    $ exttt{solution}$

    题意:将 (n) 个数分为若干组,使每一组所有数的和 (ge) 上一组所有数的和,求最大分组数。(几乎同 CSP2019 Day2T1)

    考虑处理到第 (i) 个数,设 (d[i]) 表示在这个局部解中合并了 (i-d[i]+1)(i) ,则转移方程为:

    [dp[i]=max_{j<=i~land~d[j]le sum[i]-sum[j+1]} {dp[j]+1} ]

    使用单调队列维护最大值。 代码

    (变式:改为每一组所有数的和 (le) 上一组所有数的和。将 ({a}) 翻转即可)

  • 相关阅读:
    二叉树中和为某一值的路径
    二叉搜索树的后序遍历序列(important!)
    从上往下打印二叉树
    最小的k个数(important!)
    扑克牌顺子
    栈的压入、弹出序列(important!)
    和为s的连续正数序列(important!)
    数组中只出现一次的数字
    fgets()函数以及fputs()函数
    C语言中的指针
  • 原文地址:https://www.cnblogs.com/EricQian/p/15070000.html
Copyright © 2011-2022 走看看