动态规划几种优化方式

动态规划确实是很考验思维的一类题目,有时候想到设计状态和状态转移方程还不够,还得想到它的优化方式。有的优化方式比较显然,更多的并不显然而且要依靠其他知识和外部数据结构。尽管十分灵活,但是最重要的其实也只有几种,总结经验能让我们更好地应对这个问题。

墙裂推荐博客:https://www.cnblogs.com/flashhu/p/9480669.html

蒟蒻博主也没有什么新见解啦,更多是对上面博客的一个题目集合题解。(这部分知识对博主来说还是有点难,陆陆续续学习更新吧。)

前缀和优化:

洛谷P2513

设计状态dp[i][j]为前i个数形成的逆序对对数为j的方案数,那么其实把第i个数放到不同位置能新增加[0~(i-1)]对逆序对,所以dp[i][j]=sum(dp[i-1][j-t]) (t=0~i-1)。用前缀和优化即可。

#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
const int P=10000;
int n,k,dp[N][N],sum[N];

int main()
{
    cin>>n>>k;
    dp[1][0]=1;
    for (int i=2;i<=n;i++) {
        sum[0]=dp[i-1][0]; 
        for (int j=1;j<=k;j++) sum[j]=(sum[j-1]+dp[i-1][j])%P;
        for (int j=0;j<=k;j++) {
            dp[i][j]=(sum[j]-sum[max(j-i,0)]+P)%P;
            if (j-i<0) dp[i][j]=(dp[i][j]+1)%P;
        }
    }
    printf("%d\n",dp[n][k]);
    return 0;
}
View Code

洛谷P2511

 这题忍不住像吐槽一下:一看题目很容易想到O(nm)的做法,但是一看数据量nm铁定爆炸,想了半小时没想到更优解法看题解,就是这个O(nm)的解法。吐血这数据量能过。

第一问显然可以用二分。第二问用dp,设计状态dp[i][j]代表前j个数恰好分成合法的i组的方案数,那么易得dp[i][j]=sum(dp[i-1][k-1])  (a[k]+a[k+1]+...+a[j]<=ans1)  我们发现这个是部分和,那么我们可以对每一个j预处理它的最左端a[left[j]+1]+a[left[j]+2]+...+a[i]<=ans1,那么就可以用前缀和O(1)进行转移。时间复杂度O(nm)。

#include<bits/stdc++.h>
using namespace std;
const int P=1e4+7;
const int N=5e4+10;
int n,m,a[N],b[N],lft[N],dp[N],s[N];

bool check(int M) {
    int sum=0,lst=0;
    for (int i=1;i<=n;i++)
        if (b[i]-lst>M) sum++,lst=b[i-1];    
    return sum<=m;    
}

int main()
{
    cin>>n>>m;
    int L=0,R;
    for (int i=1;i<=n;i++) scanf("%d",&a[i]),L=max(L,a[i]),b[i]=b[i-1]+a[i];
    R=b[n];
    while (L<R) {
        int M=(L+R)>>1;
        if (check(M)) R=M; else L=M+1;
    }
    
    for (int i=1;i<=n;i++) lft[i]=lower_bound(b,b+i+1,b[i]-R)-b;
    
    for (int i=1;i<=n;i++) dp[i]=(b[i]<=R),s[i]=(dp[i]+s[i-1])%P;
    int ans=(b[n]<=R);
    for (int i=2;i<=m+1;i++) {
        for (int j=n;j;j--)
            if (lft[j]-1>=0) dp[j]=(s[j-1]-s[lft[j]-1]+P)%P;
            else dp[j]=s[j-1];
        ans=(ans+dp[n])%P;
        s[0]=dp[0]; for (int j=1;j<=n;j++) s[j]=(s[j-1]+dp[j])%P;
    }
    printf("%d %d\n",R,ans);
    return 0;
}
View Code

NOIAC37 染色

这题蒟蒻博主没想出来,看题解才做出来的。设dp[i][j]代表1到i格子从i往前数j个格子(即区间[i-j+1,i])颜色不同且i和i-j颜色相同的方案数。那么可以写出转移方程。

扩大颜色不同区间:dp[i][j]+=dp[i-1][j-1]*(m-(j-1))   :在[i-j+1,i-1]区间上增加一个不同颜色

颜色不同区间不变:dp[i][j]+=dp[i-1][k]  (j<=k<=m-1)   :在[t,i-1]区间上取一个相同颜色转移得来,注意这里区间长度必须满足j<=(k=i-t)<=m-1

然后注意到上述第二个转移可以用前缀和优化O(1)转移,所以时间复杂度O(n^2)。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=5e3+10;
int n,m,p;
int dp[N][N],sum[N][N];

int main()
{
    cin>>n>>m>>p;
    
    memset(dp,0,sizeof(dp));
    dp[1][1]=m%p; for (int j=m-1;j;j--) sum[1][j]=(dp[1][j]+sum[1][j+1])%p;
    for (int i=2;i<=n;i++) {
        for (int j=1;j<m;j++) {
            dp[i][j]=(LL)dp[i-1][j-1]*(m-(j-1))%p;
            dp[i][j]=(dp[i][j]+sum[i-1][j])%p;
        }
        for (int j=m-1;j;j--) sum[i][j]=(dp[i][j]+sum[i][j+1])%p;
    }
    
    int ans=0;
    for (int j=1;j<m;j++) ans=(ans+dp[n][j])%p;
    cout<<ans<<endl;
    return 0;
} 
View Code

单调队列优化:

这种优化方式不是太复杂难度也适中有很多题目出。

P1776 宝物筛选

多重背包裸题,可以用e二进制优化,也可以用单调队列优化而且时间会更快。代码是学习《算法竞赛进阶指南》的。

#include<bits/stdc++.h>
using namespace std;
const int N=100+10;
const int M=4e4+10; 
int n,m,v[N],w[N],c[N];
int f[M],q[M];

int calc(int u,int k,int i) {
    return f[u+k*v[i]]-k*w[i];
}

int main()
{
    memset(f,0xcf,sizeof(f)); f[0]=0;  //初始化为-INF 
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) {
        scanf("%d%d%d",&w[i],&v[i],&c[i]);  //价值 体积 数量
        for (int u=0;u<v[i];u++) {
            int maxp=(m-u)/v[i];
            int l=1,r=0;
            for (int k=maxp-1;k>=max(0,maxp-c[i]);k--) {
                while (l<=r && calc(u,k,i)>=calc(u,q[r],i)) r--;
                q[++r]=k;
            }
                
            for (int p=maxp;p>=0;p--) {
                while (l<=r && q[l]>=p) l++;
                if (l<=r) f[u+p*v[i]]=max(f[u+p*v[i]],calc(u,q[l],i)+p*w[i]);
                if (p-c[i]-1>=0) {
                    while (l<=r && calc(u,p-c[i]-1,i)>=calc(u,q[r],i)) r--;
                    q[++r]=p-c[i]-1;
                }
            }
        }
    }
    int ans=0;
    for (int i=0;i<=m;i++) ans=max(ans,f[i]);
    cout<<ans<<endl;
    return 0;
}
View Code

决策单调性优化:

顾名思义,就是对于像dp(i)的每一个决策dp(j)+w,j的取值具有单调性,通过一些手段可以快速找到最优决策点而不用一个个找从而加快决策速度。

猜你喜欢

转载自www.cnblogs.com/clno1/p/10946347.html
今日推荐