1 条题解

  • 0
    @ 2025-8-24 23:00:50

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar ZHR100102
    kipfel 可爱喵

    搬运于2025-08-24 23:00:50,当前版本为作者最后更新于2025-02-28 00:16:29,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    不错的观察类 dp 题!

    观察

    首先不难发现,假设某一段数字 alara_l\sim a_r 最后全变成了 xx,那么 xx 的位置 posxpos_x 一定满足:

    • lposxrl \le pos_x \le r
    • lir,aix\forall l \le i \le r,a_i \ge x

    那么,我们就可以 O(n2)O(n^2) 求出每个数最大能覆盖的区间 alara_l\sim a_r

    并且不难发现,我们最后的答案一定是由这些连续段拼接起来的。

    dp 设计

    因为有了这个连续段性质,所以我们就比较容易 dp 了。

    本人一开始用的是 dpi,jdp_{i,j} 表示考虑到第 ii 位,第 ii 位是原数列里第 jj 个数的方案数。这种状态定义下利用前缀和优化 dp 其实是可以 O(n2)O(n^2) 得到答案的,但是转移方程细节有点多,这里不再赘述。

    但是实际上这题有更简单的一个状态定义,同样是 dpi,jdp_{i,j},但是表示的却是考虑到第 jj 位,这 jj 个位的数字所处的下标全都在 1i1 \sim i 之间的方案数是多少(这里的 i,ji,j 和前一个状态定义颠倒了一下,但是无伤大雅)。

    这两种 dp 的状态定义的唯一区别就在于一个是限制了这一位必须是 jj,一个限制了这一位只要小于等于 jj 即可。这种通过把固定某个元素转化为固定某个前缀的状态优化是一种常见的优化转移方式,无论是计数 dp 还是最优化 dp 都可以用到,并且还方便统计答案。

    那么预处理每个数能够拓展到的左右端点后直接转移即可:

    • ljr,dpi,jdpi1,j+dpi,j1l \le j \le r,dp_{i,j}\gets dp_{i-1,j}+dp_{i,j-1},即对应着接着上一个连续段的方案。
    • 否则 dpi,jdpi1,jdp_{i,j}\gets dp_{i-1,j},即对应着一个元素单独成一个连续段的情况。

    最后输出 dpn,ndp_{n,n} 即可。

    同时这个显然是可以用类似背包优化一下空间的,时间复杂度是 O(n2)O(n^2)。不优化空间其实也能过。

    代码

    不优化版本

    #include <bits/stdc++.h>
    #define fi first
    #define se second
    #define eb(x) emplace_back(x)
    #define pb(x) push_back(x)
    #define lc(x) (tr[x].ls)
    #define rc(x) (tr[x].rs)
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef long double ldb;
    using pi=pair<int,int>;
    const ll mod=1e9+7;
    const int N=5005;
    int n,h[N];
    ll dp[N][N];
    int main()
    {
        ios::sync_with_stdio(0);
        cin.tie(0);
        cout.tie(0);
        cin>>n;
        for(int i=1;i<=n;i++)cin>>h[i];
        for(int i=1;i<=n;i++)dp[i][0]=1;
        for(int i=1;i<=n;i++)
        {
            int l=i,r=i;
            while(l-1>=1&&h[l-1]>=h[i])l--;
            while(r+1<=n&&h[r+1]>=h[i])r++;
            for(int j=1;j<=n;j++)
            {
                dp[i][j]=dp[i-1][j];
                if(l<=j&&j<=r)dp[i][j]=(dp[i][j]+dp[i][j-1])%mod;
            }
        }
        cout<<dp[n][n];
        return 0;
    }
    

    优化版本

    #include <bits/stdc++.h>
    #define fi first
    #define se second
    #define eb(x) emplace_back(x)
    #define pb(x) push_back(x)
    #define lc(x) (tr[x].ls)
    #define rc(x) (tr[x].rs)
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef long double ldb;
    using pi=pair<int,int>;
    const ll mod=1e9+7;
    const int N=5005;
    int n,h[N];
    ll dp[N];
    int main()
    {
        ios::sync_with_stdio(0);
        cin.tie(0);
        cout.tie(0);
        cin>>n;
        for(int i=1;i<=n;i++)cin>>h[i];
        dp[0]=1;
        for(int i=1;i<=n;i++)
        {
            int l=i,r=i;
            while(l-1>=1&&h[l-1]>=h[i])l--;
            while(r+1<=n&&h[r+1]>=h[i])r++;
            for(int j=l;j<=r;j++)dp[j]=(dp[j]+dp[j-1])%mod;
        }
        cout<<dp[n];
        return 0;
    }
    
    • 1

    信息

    ID
    9336
    时间
    1000ms
    内存
    256MiB
    难度
    5
    标签
    递交数
    0
    已通过
    0
    上传者