动态规划几种优化方式

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

墙裂推荐博客: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;
}

洛谷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;
}

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;
} 

单调队列优化:

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

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;
}

决策单调性优化:

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

原文地址:https://www.cnblogs.com/clno1/p/10946347.html

时间: 2024-08-11 05:08:29

动态规划几种优化方式的相关文章

冒泡排序及两种优化方式

冒泡排序是最常用的小型数据排序方式,下面是用C语言实现的,及其两种优化方式. 第一种优化方式是设置一个标记位来标记是否发生了交换,如果没有发生交换就提前结束: 第二种优化方式是记录最后放生交换的位置,作为下一趟比较结束的位置. #include <stdio.h> /* * 打印数组 * */ void printArray(int arr[], int n) { int i = 0; for (i = 0; i < n; ++i) { printf("%d ", a

冒泡排序--两种优化方式

原始版本 void bubble_sort(int arr[],int n){ int tmp; for (int i = 0; i < n; ++i) { for (int j = 0; i < n; ++j) { if (arr[j] < arr[j+1]) { tmp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = tmp; } } } } 优化版本一 如果内层循环没有进行交换,说明后面的元素已经有序,则不需要继续循环.因此,我们可以设置一个标记来标

Java Web系统常用性能优化方式

在很多企业中,一些Web应用,在前期运行的时候由于用户量较小,系统压力不大,系统运营状态良好.随着时间的推移,可能由于企业内某项制度的变化,使得原本并不热门的系统,变成了大家日常都需要去使用的,由于用户量激增,原本系统设计和实现中存在的缺陷大量的暴露,而最不能让人容忍的是,系统的响应时间变长,甚至在某时段用户集中访问时,这一现象会变得让人难以忍受.这往往会导致相关业务部门和负责系统运维的技术人员承受很大的压力.本文将介绍如何针对这些的系统进行相应的优化和改进,即使没有相关的系统需要优化,也可以在

逆向课程第三讲逆向中的优化方式,以及加减乘

逆向课程第三讲逆向中的优化方式,以及加减乘 一丶为什么要熟悉优化方式 熟悉优化方式,可以在看高级代码的时候浮现出汇编代码,以及做逆向对抗的时候,了解汇编代码混淆 优化和混淆是相反的 优化: 指的是汇编代码越少越好,让程序更快的执行 混淆: 一条汇编代码变为多条汇编代码,影响逆向人员的破解能力,但是软件的效率大大降低 二丶加减乘的常见的几种优化方式 优化方式分为: 1.常量折叠 2.常量传播 3.变量去除 这些优化方式成为窥孔优化 (有10几种后面会一一到来) 首先了解什么是常量折叠,常量传播,然

关于SPFA算法的优化方式

关于SPFA算法的优化方式 这篇随笔讲解信息学奥林匹克竞赛中图论部分的求最短路算法SPFA的两种优化方式.学习这两种优化算法需要有SPFA朴素算法的学习经验.在本随笔中SPFA朴素算法的相关知识将不予赘述. 上课! No.1 SLF优化(Small Label First) 顾名思义,这种优化采用的方式是把较小元素提前. 就像dijkstra算法的堆优化一样.我们在求解最短路算法的时候是采取对图的遍历,每次求最小边的一个过程,为了寻找最小边,我们需要枚举每一条出边,如果我们一上来就找到这个边,那

&quot;HybridDB &#183; 性能优化 &#183; Count Distinct的几种实现方式” 读后感

原文地址:HybridDB · 性能优化 · Count Distinct的几种实现方式 HybridDB是阿里基于GreenPlum开发的一款MPP分析性数据库,而GreenPlum本身基于PostgreSQL. 如此,HybridDB的优化思路和手段难免会受到PostgreSQL影响和限制. 文中的语句最终优化得到了几个不同计划,其优化的语句简化后形如 select count(distinct c1) from t group by c2; 这条语句在HybridDB下实现: 每个服务器自

002-多线程-锁-同步锁-synchronized几种加锁方式、Java对象头和Monitor、Mutex Lock、JDK1.6对synchronized锁的优化实现

一.synchronized概述基本使用 为确保共享变量不会出现并发问题,通常会对修改共享变量的代码块用synchronized加锁,确保同一时刻只有一个线程在修改共享变量,从而避免并发问题. synchronized结论: 1.java5.0之前,协调线程间对共享对象的访问的机制只有synchronized和volatile,但是内置锁在功能上存在一些局限性,jdk5增加了Lock以及ReentrantLock. 2.java5.0,增加了一种新的机制:显式锁ReentrantLock,注意它

Redis两种持久化方式(RDB&amp;AOF)

爬虫和转载请注明原文地址;博客园蜗牛:http://www.cnblogs.com/tdws/p/5754706.html Redis所需内存 超过可用内存怎么办 Redis修改数据多线程并发—Redis并发锁 windows下redis基础操作与主从复制 从而 数据备份和读写分离 Redis两种持久化方式(RDB&AOF) Redis的持久化过程中并不需要我们开发人员过多的参与,我们要做的是什么呢?除了深入了解RDB和AOF的作用原理,剩下的就是根据实际情况来制定合适的策略了,再复杂一点,也就

js的6种继承方式

重新理解js的6种继承方式 注:本文引用于http://www.cnblogs.com/ayqy/p/4471638.html 重点看第三点 组合继承(最常用) 写在前面 一直不喜欢JS的OOP,在学习阶段好像也用不到,总觉得JS的OOP不伦不类的,可能是因为先接触了Java,所以对JS的OO部分有些抵触. 偏见归偏见,既然面试官问到了JS的OOP,那么说明这东西肯定是有用的,应该抛开偏见,认真地了解一下 约定 P.S.下面将展开一个有点长的故事,所以有必要提前约定共同语言: 1 2 3 4 5