DP的优化 1
1.0单调队列
在计算形如$ dp[i]=max(dp[j])+C $ \(的柿子时,若我们可以O(1)计算出\) \(max(dp[j])\),那么时间复杂度就会大大降低,\(O(n^2)\to O(n)\)
显然在\(max(dp[j])\)中,只有最大值有用,所以我们就可以利用单调队列,维护区间最大值,进行求解.
例题:
I P1725 琪露诺
单调队列板子题
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
int n,l,r;
int w[maxn*2];
int dp[maxn*2],ans=-0x3f3f3f3f;
struct node{
int p,a;
}q[maxn];
int rd(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
int main(){
// freopen("A.in","r",stdin);
// freopen("A.out","w",stdout);
n=rd();l=rd();r=rd();
for(int i=0;i<=n;i++) w[i]=rd();
int l1=0,r1=0;
for(int i=1;i<=n+r;i++) dp[i]=-0x3f3f3f3f;
dp[0]=0;
for(int i=l;i<=n+r;i++){
while(l1<=r1&&q[r1].p>i-l) r1--;
while(l1<=r1&&q[l1].p<i-r) l1++;
dp[i]=q[l1].a+w[i];
while(l1<=r1&&dp[i-l+1]>q[r1].a) r1--;
q[++r1].a=dp[i-l+1];
q[r1].p=i-l+1;
}
for(int i=n+1;i<=n+r;i++) ans=max(ans,dp[i]);
cout<<ans;
return 0;
}
II P2254 [NOI2005] 瑰丽华尔兹
单调队列进阶版,需要进行一定的转化.(好久之前做的,以后来补题解)
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e2+5;
int n,m,x,y,k;
char mapp[maxn][maxn];
int dp[maxn][maxn][maxn];
struct node{
int q1,v;
}q[maxn];
int main(){
cin>>n>>m>>x>>y>>k;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>mapp[i][j];
for(int k1=0;k1<=k;k1++)
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
dp[k1][i][j]=-0x3f3f3f3f;
dp[0][x][y]=dp[1][x][y]=0;
for(int k1=1;k1<=k;k1++){
int s,t,d;
int l=1,r=0;
cin>>s>>t>>d;
int len=t-s+1;
if(d==1){
for(int j=1;j<=m;j++){
l=1;r=0;
int f=1;
for(int i=n;i>=1;i--){
if(mapp[i][j]=='x'){l=1,r=0;continue;}
// if(dp[k1-1][i][j]>0&&f==1)
// dp[k1][i][j]=max(dp[k1][i][j],dp[k1-1][i][j]),f=0;
while(l<=r&&q[l].q1>i+len) l++;
while(dp[k1-1][q[r].q1][j]+q[r].q1<dp[k1-1][i][j]+i&&l<=r) r--;
q[++r].q1=i;
q[r].v=dp[k1-1][i][j];
dp[k1][i][j]=max(dp[k1-1][i][j],q[l].v+q[l].q1-i);
// if(j==3) cout<<dp[k1][i][j]<<endl;
}
}
}
if(d==2){
for(int j=1;j<=m;j++){
l=1,r=0;
int f=1;
for(int i=1;i<=n;i++){
if(mapp[i][j]=='x'){l=1,r=0;continue;}
// if(dp[k1-1][i][j]>0&&f==1)
// dp[k1][i][j]=max(dp[k1][i][j],dp[k1-1][i][j]),f=0;
while(l<=r&&q[l].q1<i-len) l++;
while(dp[k1-1][q[r].q1][j]-q[r].q1<dp[k1-1][i][j]-i&&l<=r) r--;
q[++r].q1=i;
q[r].v=dp[k1-1][i][j];
dp[k1][i][j]=max(dp[k1-1][i][j],q[l].v+i-q[l].q1);
}
}
}
if(d==3){
for(int i=1;i<=n;i++){
l=1,r=0;
int f=1;
for(int j=m;j>=1;j--){
if(mapp[i][j]=='x'){l=1,r=0;continue;}
// if(dp[k1-1][i][j]>0&&f==1)
// {dp[k1][i][j]=max(dp[k1][i][j],dp[k1-1][i][j]);f=0;}
while(l<=r&&q[l].q1>j+len) l++;
while(dp[k1-1][i][q[r].q1]+q[r].q1<dp[k1-1][i][j]+j&&l<=r) r--;
q[++r].q1=j;
q[r].v=dp[k1-1][i][j];
dp[k1][i][j]=max(dp[k1-1][i][j],q[l].v+q[l].q1-j);
// if(i==2) cout<<dp[k1][i][j]<<endl;
}
}
}
if(d==4){
for(int i=1;i<=n;i++){
l=1,r=0;
int f=1;
for(int j=1;j<=m;j++){
if(mapp[i][j]=='x'){l=1,r=0;continue;}
// if(dp[k1-1][i][j]>0&&f==1)
// dp[k1][i][j]=max(dp[k1][i][j],dp[k1-1][i][j]),f=0;
while(l<=r&&q[l].q1<j-len) l++;
while(dp[k1-1][i][q[r].q1]-q[r].q1<dp[k1-1][i][j]-j&&l<=r) r--;
q[++r].q1=j;
q[r].v=dp[k1-1][i][j];
dp[k1][i][j]=max(dp[k1-1][i][j],q[l].v+j-q[l].q1);
// if(i==4) cout<<dp[k1][i][j]<<endl;
}
}
}
}
int ans=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
ans=max(ans,dp[k][i][j]);
cout<<ans;
return 0;
}
IIIPOJ1821 Fence
先将s排序,便于按照顺序进行dp。定义\(dp[i][j]\)为前\(i\)个人涂前\(j\)块木板的最大答案.
考虑当前这个人涂或者不涂,是否涂第\(j\)块木板容易写出
\(dp[i][j]=max(dp[i-1][j],dp[i][j-1])\)
\(dp[i][j]=max_{j-l[i]+1\leq k\leq s[i]-1} (dp[i-1][k-1]+(j-k+1)*p[i])\)(\(k表示涂k到j这个区间的木板\))
\(dp[i][j]=max_{j-l[i]+1\leq k\leq s[i]-1} (dp[i-1][k-1]-k*p[i])+(j+1)*p[i]\)(分离决策变量k和状态变量j)
注意到\(k\)的作用就是枚举求出对应\(j\)的区间最值,于是有
当我们转移\(dp[i][j]\),循环到\(j\)时,注意到\(j\)每加一位,区间也会随之向右线性变化.
只需利用单调队列预先处理出这个包含\(s[i]\)的区间最值.再动态维护合法区间即可.
一道简单题还调了好久
代码:
点击查看代码
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1e2+10;
int n,k,p[maxn],s[maxn],l[maxn];
int dp[maxn][16010];
struct node{
int p1,value;
}q[16010];
struct node1{
int l,s,p;
}e[maxn];
int rd(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
char ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
bool cmp(node1 x,node1 y){
return x.s<y.s;
}
int main(){
n=rd();k=rd();
for(int i=1;i<=k;i++){
e[i].l=rd();
e[i].p=rd();
e[i].s=rd();
}
sort(e+1,e+k+1,cmp);
for(int i=1;i<=k;i++)
for(int j=1;j<=n;j++)
dp[i][j]=-0x3f3f3f3f;
for(int i=1;i<=k;i++){
int l1=1,r1=0;
for(int j=max(1,e[i].s-e[i].l+1);j<=e[i].s;j++){
while(l1<=r1&&dp[i-1][j-1]-e[i].p*j>=q[r1].value) r1--;
q[++r1].value=dp[i-1][j-1]-e[i].p*j;
q[r1].p1=j;
}
for(int j=1;j<=n;j++){
dp[i][j]=max(dp[i][j-1],dp[i-1][j]);
if(j>=e[i].s){
while(l1<=r1&&q[l1].p1<j-e[i].l+1) l1++;
if(l1<=r1) dp[i][j]=max(dp[i][j],q[l1].value+e[i].p*(j+1));
}
}
}
cout<<dp[k][n]<<endl;
return 0;
}
1.1单调队列优化多重背包
I P3423 [POI2005]BAN-Bank Notes
代码:

浙公网安备 33010602011771号