【XSY3952】简单的计数题(dp)

题面

简单的计数题

题解

首先题意可以转化为:给你一个长度为 \(n\) 的序列 \(c\),求将 \(c\) 分成两个长度为 \(\dfrac{n}{2}\) 的相同的子序列的方案数。

考虑 dp,设 \(f(i,sta)\) 表示已经将 \(c\) 的前 \(i\) 位分成了两个子序列,其中长的子序列比短的子序列多出来的未匹配的部分为 \(sta\)(用 dequelist等STL容器均可记录)的方案数。不妨称这个 \(sta\) 为状态,那么只有当状态长度不大于 \(\dfrac{n}{2}\) 时,这个状态才是有用的。

转移十分简单:新加入一个元素时,考虑是继续扔到当前状态的尾部,还是和当前状态的开头匹配并删除这个开头。

这里主要讲为什么时间是对的:

时间复杂度和状态数有关。假设当前到第 \(i\) 位,一共有 \(x\) 个状态,考虑接下来的 \(c\) 的两个元素 \(a,b\)

  • 如果他们相等,那么 “\(a\) 删开头扔尾部” 和 “\(b\) 删开头扔尾部” 得到的状态一样,所以新状态共 \(3x\) 种。
  • 如果他们不相等,那么 “\(a\) 删开头扔尾部” 和 “\(b\) 删开头扔尾部” 两种中仅有一种是可行的,否则他们相等,矛盾。所以新状态共 \(3x\) 种。

于是此时总状态不超过 \(3^{\tfrac{n}{2}}\) 种。但还是太多了。

由于我们只需要知道 \(f(n,\empty)\),所以考虑折半:将 \(c\) 序列的前一半和后一半(要翻转)分别 dp,最后将前后两半的 dp 值合并起来统计答案。

此时状态数不超过 \(3^{\tfrac{n}{4}}\) 种。

然后题解说进一步分析(?)可以证明在 \(n=60\) 时状态数不超过 \(50000\)

代码如下:

#include<bits/stdc++.h>
 
#define N 65
 
using namespace std;
 
namespace modular
{
    const int mod=998244353;
    const int inv2=499122177;
    inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
    inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
    inline int mul(int x,int y){return 1ll*x*y%mod;}
}using namespace modular;
 
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=(x<<1)+(x<<3)+(ch^'0');
        ch=getchar();
    }
    return x*f;
}
 
typedef deque<int> STA;
 
int T,n,hn,a[N];
 
STA now1,now2;
map<STA,int>dp[2][N>>1];
 
void solve(bool opt)
{
    for(int i=0;i<=hn;i++) dp[opt][i].clear();
    now1.clear();
    dp[opt][0][now1]=1;
    for(int i=0;i<hn;i++)
    {
        for(map<STA,int>::iterator it=dp[opt][i].begin();it!=dp[opt][i].end();it++)
        {
            now1=(*it).first;
            if((!now1.empty())&&now1.front()==a[i+1])
            {
                now2=now1;
                now2.pop_front();
                dp[opt][i+1][now2]=add(dp[opt][i+1][now2],dp[opt][i][now1]);
            }
            now2=now1;
            now2.push_back(a[i+1]);
            if(!now1.empty()) dp[opt][i+1][now2]=add(dp[opt][i+1][now2],dp[opt][i][now1]);
            else dp[opt][i+1][now2]=add(dp[opt][i+1][now2],mul(dp[opt][i][now1],2));
        }
    }
}
 
int main()
{
    T=read();
    while(T--)
    {
        n=read();
        hn=n>>1;
        for(int i=1;i<=n;i++) a[i]=read();
        solve(0);
        reverse(a+1,a+n+1);
        solve(1);
        int ans=0;
        for(map<STA,int>::iterator it=dp[0][hn].begin();it!=dp[0][hn].end();it++)
        {
            now1=(*it).first;
            while(!now2.empty()) now2.pop_front();
            while(!now1.empty())
            {
                now2.push_back(now1.back());
                now1.pop_back();
            }
            if(!now2.empty()) ans=add(ans,mul((*it).second,mul(dp[1][hn][now2],inv2)));
            else ans=add(ans,mul((*it).second,dp[1][hn][now2]));
        }
        printf("%d\n",ans);
    }
    return 0;
}
/*
5
2
1 1
2
2 2
4
1 1 2 2
6
1 2 3 4 5 6
4
1 2 2 1
*/

实测 dequelist快,我直接疑惑了。

posted @ 2022-10-30 14:26  ez_lcw  阅读(18)  评论(0编辑  收藏  举报