拆数组- $dp$ ,单调栈

拆数组- \(dp\) ,单调栈

Array Collapse CF-1913D

题意

定义消除操作是删除一段区间除去最小外的所有数,给定原数组问有多少种新的数组可以由原数组得到。

思路

由于数据量过大,所以我们考虑线性 \(dp\) ,考虑定义 \(dp_i\) 为以 \(i\) 为结尾时方案数,没什么好因为的,这样子方便转移和操作,仔细手搓一下样例会发现最后一个点的前驱并不是所有的点,前面某些点会因为不够小而无法替代中间的一些节点,从而无法从次转移,不过要注意的是只要还没有出现比当前节点小的点,那么中间的点总是可以转移的,因为他们可以被当前点删除。如下图中 \(D\) 可以由 \(B C\) 转移而来。

pZEEMuV.png

现在先来考虑一下答案的统计,似乎好像是以每个点结尾的所有可能之和,但是和刚才一样,并不是全部点都是可以作为整个串的结尾的,只有比后面所有点小的点(包括最后一个点)可以作为结尾,而其他节点的作用就是在限制之下递推出可行的方案(最后一个点的考虑也是一样的,要满足要求限制)。

最后,每一次从前面找小的点在转移的时间复杂度是 \(O(n^2)\) ,此时很容易想到使用单调栈优化,因为找的是前面比它小的值且可以递增。

code

#include <bits/stdc++.h>
#define int long long
using namespace std;
constexpr int maxn = 3e5+10;
constexpr int mod = 998244353;
constexpr int INF = 0x3f3f3f3f3f3f3f3f;
void read(int &);

int wi[maxn];
int dp[maxn];
int stk[maxn];// 存比当前节点wi小的id
int sum[maxn];

inline void add(int &x,int y)
{
    x+=y;
    if(x>=mod)
    {
        x-=mod;
    }
    else if(x<0)
    {
        x+=mod;
    }
}

void solve()
{
    int n;
    read(n);
    for(int i=1;i<=n;++i)
    {
        read(wi[i]);
    }
    int stsum=0,top=0;
    int ans=0;
    for(int i=1;i<=n;++i)
    {
        while(top && wi[stk[top]]>wi[i])
        {
            add(stsum,-dp[stk[top--]]);// 维护单调栈
        }
        dp[i]=(stsum+sum[i-1]-sum[stk[top]]+(top==0))%mod;// 栈和+比它小的点->当前点+如果是当前最小

        sum[i]=(sum[i-1]+dp[i])%mod;
        add(stsum,dp[i]);
        stk[++top]=i;
    }
    ans=stsum%mod;
    printf("%lld\n",ans);
}

signed main()
{
    #ifndef ONLINE_JUDGE
    freopen("cjdl.in","r",stdin);
    freopen("cjdl.out","w",stdout);
    #endif // ONLINE_JUDGE

    int t;
    read(t);
    while(t--)
    {
        solve();
    }

    return 0;
}

inline void read(int &x)
{
    x=0;
    int f=1;
    signed c=getchar();
    while(!isdigit(c))
    {
        if(c=='-')
        {
            f=-1;
        }
        c=getchar();
    }
    while(isdigit(c))
    {
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    }
    x*=f;
}
posted @ 2025-11-27 15:38  玖玮  阅读(3)  评论(0)    收藏  举报