单调队列优化DP

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

主要内容

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

\[dp[i]=\max_{L_i\le j\le 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}^{k\le k[i]}{\{dp[i-1][j-k\times c[i]]+k\times w[i]\}} \]

考虑 \(dp\) 的转移 。

\[0\le p < c[i],0\le j \le \left\lfloor \dfrac{V-p}{c[i]}\right\rfloor,0\le k \le k[i] \]
\[dp[i][p+j\times c]=\max{\{dp[i-1][p+(j-k)\times c]+k\times w\}} \]
\[dp[i][p+j\times c]=\max{\{dp[i-1][p+(j-k)\times c]-(j-k)\times w+j\times w\}} \]
\[dp[i][p+j\times c]=\max{\{dp[i-1][p+(j-k)\times c]-(j-k)\times w\}}+j\times 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(V\sum_{i=1}^{n}\log_2{k_i})\) ,见背包问题 。

注意:

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

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


二维单调队列

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

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

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

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


例题

P1725 琪露诺

$\texttt{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\n",ans);

P3572 [POI2014]PTA-Little Bird

$\texttt{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\n",dp[n]);

P3957 跳房子

$\texttt{solution}$

注意点:

  1. 存储要开 \(\texttt{long}~\texttt{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\n",ans);

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

$\texttt{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\n",ans);//fclose(stdin);//fclose(stdout);return 0;
}

CF372C Watching Fireworks is Fun

$\texttt{solution}$

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

转移方程:

\[j-(t[i]-t[i-1])\times d\le k\le j+(t[i]-t[i-1])\times 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\n",ans);//fclose(stdin);//fclose(stdout);return 0;
}

烧桥计划

$\texttt{solution}$

题意:

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

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

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

\(n\leq 10^5,1000\leq 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\times a[i] \]

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

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

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

\(step~2\)

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

因此 \(\texttt{选择删除的点数}\le 2\sqrt{n}\) ( \(j\le 2\sqrt{n}\) ) 。

\(step~3\)

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

转移可以由两部分组成:

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

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

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

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

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

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

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

时间复杂度:\(O(n\sqrt{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\n",ans);

P2254 [NOI2005]瑰丽华尔兹

$\texttt{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\n",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\n",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\n",ans);

P2569 [SCOI2010]股票交易

$\texttt{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\n",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]\times 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\n",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\n",ans);

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

P2300 合并神犇

$\texttt{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\}\) 翻转即可)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/318367.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CF1322B-Present【双指针】

正题 题目链接:https://www.luogu.com.cn/problem/CF1322B 题目大意 给出nnn个数字aia_iai​求 ⨁i1n⨁ji1n(aiaj)\bigoplus _{i1}^n\bigoplus _{ji1}^n(a_ia_j)i1⨁n​ji1⨁n​(ai​aj​) 1≤n≤4105,1≤ai≤1071\leq n\leq 4\times 10^5,1\leq a_i\leq 10^71≤n≤4105,1≤a…

多体问题

代码&#xff1a; function SunEarthMoon % M函数文件load planets; % 将planets.mat中的变量mass、position、velocity加载过来[sun, earth, moon] deal(18, 3, 25); % sun、earth、moon分别是18、3、25行 list [sun, earth, moon]; % 1行3列矩阵 G 6.67e-11; % gr…

【CF1179 A,B,C】Valeriy and Deque / Tolik and His Uncle / Serge and Dining Room

还好题很温柔&#xff0c;温柔得我差点没做完 文章目录A&#xff1a;Valeriy and Deque题意题解代码实现B&#xff1a;Tolik and His Uncle题目题解代码实现C&#xff1a;Serge and Dining Room题目题解代码实现A&#xff1a;Valeriy and Deque 题意 给定一个双端队列&#…

YBTOJ:比赛得分(期望)

文章目录题目描述解析代码题目描述 解析 不太难的题 显然本题在AB队员大小关系相反时其对答案的贡献互为相反数。 所以想到把B队队员sort一下后就可以二分找到大小关系相反的分界点 然后维护和与平方和两个前缀数组搞一搞即可O1求出贡献 总复杂度&#xff1a;nlognnlognnlogn …

Matlab与高等数学

曲线与曲面画图 平面 对于不同曲线的表达式&#xff0c;Matlab中有不同的绘图命令&#xff0c;主要有 plot, fplot, ezplot&#xff0c;plot3&#xff0c;polar&#xff0c; 曲面 1.2 曲面画图 曲面的一般方程是F(x,y,z)0&#xff0c;一般需要将曲面的点坐标先表示出来&…

[USACO19JAN,Platinum] Redistricting

[USACO19JAN,Platinum] Redistricting 这道题A了才知道。。并不难a&#xff01; orz 题目 内存限制&#xff1a;128 MiB 时间限制&#xff1a;1000 ms 题目描述 奶牛们的最大城市Bovinopolis正在重新划分势力范围—生活在那里的主要是两个品种的奶牛&#xff08;Holsteins和…

.NET Core + JWT令牌认证 + Vue.js 通用动态权限(RBAC)管理系统框架[DncZeus]开源啦!!!...

DncZeus前言关于 DncZeusDncZeus Dnc Zeus"Dnc"--.Net Core 的缩写&#xff1b;"Zeus"--中文译为宙斯&#xff0c;是古希腊神话中的众神之王&#xff0c;奥林匹斯十二主神之首&#xff0c;统治宇宙万物的至高无上的主神&#xff08;在古希腊神话中主神专…

[gdoi2018 day1]小学生图论题【分治NTT】

正题 题目大意 一张随机的nnn个点的竞赛图&#xff0c;给出它的mmm条相互无交简单路径&#xff0c;求这张竞赛图的期望强联通分量个数。 1≤n,m≤1051\leq n,m\leq 10^51≤n,m≤105 解题思路 先考虑m0m0m0的做法&#xff0c;此时我们考虑一个强联通块的贡献&#xff0c;注意到…

背包问题 DP

各种各样的基础背包 0-1 背包 非常朴素&#xff0c;复杂度 \(O(nV)\) void z_o_pack(int c,int v) {for(int iV;i>c;i--)dp[i]max(dp[i],dp[i-c]v); } 完全背包 复杂度 \(O(nV)\) void comp_pack(int c,int v) {for(int ic;i<V;i)dp[i]max(dp[i],dp[i-c]v); } 多重背包 单…

P5081 Tweetuzki爱取球(期望)(线性求逆元)

文章目录题目描述解析代码题目描述 解析 首先有一个很重要的引理&#xff1a; 若一件事做成的概率是p&#xff0c;则其做成需要次数的期望是1/p 为什么呢&#xff1f; 我们设做成这件事的期望次数是x 就可以列出方程&#xff1a; x1p∗0(1−p)∗xx1p*0(1-p)*xx1p∗0(1−p)∗x …

Matlab与线性代数

文章目录多项式求解1.2 多项式四则运算1.3 多项式的分解与合并行列式求解3、矩阵基本运算➢ 3.2 矩阵的取块和变换➢ 3.3 矩阵的基本运算4、求解线性方程组多项式求解 ➢ 1.1 多项式表达式与根 有关多项式函数表达式与根的Matlab命令&#xff1a; poly2sym 返回由多项式系数转…

【 CF1186D,E,F】Vus the Cossack and Numbers/Vus the Cossack and a Field/Vus the Cossack and a Graph

太ex了&#xff0c;哭了哭了orz 后面两道平均一道花了我一天啊&#xff01; 文章目录D&#xff1a;Vus the Cossack and Numbers题意翻译题解代码实现E&#xff1a;Vus the Cossack and a Field题意翻译题解代码实现F:Vus the Cossack and a Graph题目暴力题解代码实现官方题解…

IdentityServer4与ocelot实现认证与客户端统一入口

关于IdentityServer4与ocelot博客园里已经有很多介绍我这里就不再重复了。ocelot与IdentityServer4组合认证博客园里也有很多&#xff0c;但大多使用ocelot内置的认证&#xff0c;而且大多都是用来认证API的&#xff0c;查找了很多资料也没看到如何认证oidc&#xff0c;所以这里…

CF1556D-Take a Guess【交互】

正题 题目链接:https://codeforces.com/contest/1556/problem/D 题目大意 现在有nnn个你不知道的数字&#xff0c;你有两种询问操作 询问两个下标的数字的andandand询问两个下标的数字的ororor 要求在2n2n2n次操作以内求出第kkk小的数字 1≤n≤104,0≤ai≤1091\leq n\leq 1…

YBTOJ:彩球抽取(期望)

文章目录题目描述解析代码题目描述 解析 首先&#xff0c;可以使用dp解决本题 设fi,j,k&#xff1a;操作i轮之后编号j的小球有k个的概率 转移和统计答案就都不难了 但是还有一个问题 不难发现这个题循环下去是可以无穷无尽的 所以限定一个i的上界&#xff08;如500000&#xf…

魔改森林

题意&#xff1a; 曾经有一道叫做迷雾森林的题目&#xff0c;然而牛牛认为地图中的障碍太多&#xff0c;实在是太难了&#xff0c;所以删去了很多点&#xff0c;出了这道题。 牛牛给出了一个n行m列的网格图 初始牛牛处在最左下角的格点上(n1,1)&#xff0c;终点在右上角的格点…

基于IdentityServer4 实现.NET Core的认证授权

IdentityServer4是什么&#xff1f;IdentityServer4是基于ASP.NET Core实现的认证和授权框架&#xff0c;是对OpenID Connect和OAuth 2.0协议的实现。OpenID Connect 和 OAuth2.0是什么OpenID Connect:OpenID Connect由OpenID基金会于2014年发布的一个开放标准, 是建立在OAuth …

常见存储、查找算法

存储 散列存储&#xff1a;即哈希的存储方式。 顺序存储&#xff1a;数组的存储方式 链式存储&#xff1a;链式前向星、vector<> 压缩存储 索引存储 查找 常见查找算法 顺序查找 一个一个往下找&#xff0c;复杂度 \(O(\dfrac{n1}{2})\) 。 适合顺序存储&#xff0c…

CF1556E-Equilibrium【栈,树状数组】

正题 题目连接:https://codeforces.com/contest/1556/problem/E 题目大意 两个长度为nnn的序列a,ba,ba,b&#xff0c;qqq次询问一个区间[l,r][l,r][l,r]。 在这个区间中你每次可以选择一个长度为偶数的下标递增的序列&#xff0c;让奇数位置的aaa加一&#xff0c;偶数位置的…

[COCI 2018#5]Parametriziran

这道题呢&#xff01; 算了&#xff0c;不要让这玩意儿活着祸害众生吧&#xff01;让我们来拯救苍生于苦海之中&#xff01;&#xff01; 骚话连篇ing 题目 由小写英文字母和问号组成的字符串成为参数化单词&#xff08;例如&#xff1a;??cd,bcd,??&#xff09;。如果两…