• DP优化


    单调队列优化

    前置技能:单调队列(经典的问题模型:洛谷P1886 滑动窗口

    对于序列中的每个点,从其左侧一段决策区间内的最值进行转移,且决策区间随着序列下标的增大也不断右移(就像窗口向右滑动)。

    j<k,容易发现如果gj劣于gk的话,那么当决策区间移动到k以后,j永远不会成为最优决策点,再也不会被转移了。

    于是,我们只要维护一个队列,满足下标递增,决策性递减。我们需要当前的队首成为最优决策点,那么当队首第一次超出了区间范围(以后也就永远超出了)就把它出队。为了保证单调性,队尾新加入点之前,要先把队列中比它劣的点依次从队尾出队。

    题解:

    两个单调队列一个最大一个最小

    然后?

    就可以了啊

    1、维护队首(就是如果你已经是当前的m个之前那你就可以被删了,head++)

    2、在队尾插入(每插入一个就要从队尾开始往前去除冗杂状态)

    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #include<cstdio>
    #include<cstdlib>
    #include<cmath>
    using namespace std;
    int n,m;
    int q1[1000001],q2[1000001];
    int a[1000001];
    int min_deque()
    {
        int h=1,t=0;
        for(int i=1;i<=n;i++)
        {
            while(h<=t&&q1[h]+m<=i) h++;
            while(h<=t&&a[i]<a[q1[t]]) t--;
            q1[++t]=i;
            if(i>=m) printf("%d ",a[q1[h]]);
        }
        cout<<endl;
    }
    int max_deque()
    {
        int h=1,t=0;
        for(int i=1;i<=n;i++)
        {
            while(h<=t&&q2[h]+m<=i) h++;
            while(h<=t&&a[i]>a[q2[t]]) t--;
            q2[++t]=i;
            if(i>=m) printf("%d ",a[q2[h]]);
        }
    }
    int main()
    {
        cin>>n>>m;
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        min_deque();
        max_deque();
        return 0;
    }
    View Code

    前缀和优化

    当DP过程中需要反复从一个求和式转移的话,可以先把它预处理一下。运算一般都要满足可减性。

    比较简单就不展开了

    洛谷P2513 [HAOI2009]逆序对数列

    题解:

    考虑i的放置,i为最大值,所以放在i-1个位置都可以计算出对答案的贡献

    dp[i][j]=Σdp[i-1][k] (j-i+1 <=k<= j)

    特别的到i时最多可以贡献i-1对逆序对,所以从dp[0]~dp[j-i+1]这一段不能加

    n^3超时,可用前缀和优化

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #define N 1001
    #define mod 10000
    using namespace std;
    int dp[N][N];
    int n,k,ans,sum;
    int main()
    {
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;i++) dp[i][0]=1;
        for(int i=2;i<=n;i++)
        {
            sum=0;
            for(int j=0;j<=k;j++)
            {
                (sum+=dp[i-1][j])%mod;
                dp[i][j]=sum%mod;
                if(j-i+1>=0)((sum-=dp[i-1][j-i+1])+=mod)%mod;
            }
        }
        printf("%d
    ",dp[n][k]);
        return 0;
    }
    View Code

    斜率优化

    与决策单调性有着说不清的联系。

    考虑两个决策j1,j2,如果j1j2优,那么gj1+wi,j1gj2+wi,j2

    这时候根据题目特点把ww展开,如果式子能化成yj1yj2xj1xj2ki的形式,那么我们把每个决策看成(xj,yj)分布在坐标系上,而真正有用的决策点实际上形成了一个凸壳。(由类似线性规划的寻找最优解的过程可以发现)

    下面第一题对两种理解斜率优化的方法做了更详细的分析(最好看第二种,因为第一种有针对性,不通用)

    于是我们用数据结构维护凸壳上的所有点,具体实现依题而定。

    不管是什么题,加入决策点的时候要保证斜率递增/递减。

    如果x单调,可以用单调栈维护凸壳,转移时使用当前直线的斜率(线性规划),在栈内二分最优解。

    如果斜率k单调,那么用单调队列维护凸壳,队首为当前最优决策。转移之前如果队首不优就出队。

    洛谷P2900 [USACO08MAR]土地征用Land Acquisition

     题解:

    斜率优化DP入门题。

    观察题目发现如果有两块土地 i, ji,j 满足 xi ≤ xj , yi ≤ yj i 的影响就可以不用考虑。
    因此把土地按长度从小到大排序,则它们的宽度递减。可以发现每次购买的土地一 定是连续一段。
    我们设 f(i)表示买前 i块需要的最小代价

    斜率优化,维护一个下凸包即可。

    #include <bits/stdc++.h>
    using namespace std;
    
    inline int read()
    {
        int x=0; int f=1; char ch=getchar();
        while(!isdigit(ch)) {if(ch=='-')f=-1; ch=getchar();}
        while(isdigit(ch)) {x=x*10+ch-'0'; ch=getchar();}
        return x*f;
    }
    
    const int MAXN = 100005;
    struct data {long long x,y;} a[MAXN];
    long long x[MAXN],y[MAXN],f[MAXN],q[MAXN];
    int n,tot;
    
    inline bool cmp(data a,data b)
    {return a.x==b.x?a.y<b.y:a.x<b.x;}
    
    inline double slop(int a,int b)
    {return (double)(f[b]-f[a])/(y[a+1]-y[b+1]);}
    
    int main(int argc, char const *argv[])
    {
        n=read();
        for(int i=1; i<=n; i++)
            a[i].x=read(),a[i].y=read();
        sort(a+1,a+n+1,cmp);
        for(int i=1; i<=n; i++)
        {
            while(tot && a[i].y>=y[tot]) tot--;
            x[++tot]=a[i].x; y[tot]=a[i].y;
        }
        int l=0,r=0;
        for(int i=1; i<=tot; i++)
        {
            while(l<r && slop(q[l],q[l+1])<x[i]) l++;
            int t=q[l]; f[i]=f[t]+y[t+1]*x[i];
            while(l<r && slop(q[r],i)<slop(q[r-1],q[r])) r--;
            q[++r]=i;
        }
        printf("%lld",f[tot]);
        return 0;
    }
    View Code

    二分栈

    我们使用决策二分栈(一种单调栈)来维护所有有用的决策,其中栈顶是当前最优决策。

    为什么叫二分栈呢?

    我们可以把gj+wi,j视为关于j的函数。因为决策单调,所以对于栈中的任意相邻两个决策点,我们都可以通过二分找到一个临界值k,使得序列中在k之前的时候,其中一个作为决策转移到fk更优,而k以后另一个更优。可以借助函数图像来理解这个过程。

    我们需要栈顶为当前的最优解。而如果栈中有不止一个元素,则可能存在一个i,使得到ii之后栈里面的决策比栈顶优了。这个时候,如何快速判断并弹掉栈顶呢?

    上面提到的这个可二分的性质就派上用场了。对于当前的i,如果当前栈顶下面与栈顶相邻的决策在i之前就比栈顶更优了,就要把栈顶弹掉。

    洛谷P1912 [NOI2009]诗人小G

  • 相关阅读:
    Final-阶段站立会议5
    Debug阶段成员贡献分
    每周例行报告——第九周
    beta发布简评
    简易四则运算生成程序——批量出题
    每周例行报告——第八周
    每周例行报告——第七周
    每周例行报告——第六周
    课堂作业:alpha发布点评
    “四则运算生成程序——GUI支持和部分功能改进”链接
  • 原文地址:https://www.cnblogs.com/Heartbeat358/p/12669770.html
Copyright © 2020-2023  润新知