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

题面

简单的计数题

题解

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

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

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

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

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

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

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

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

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

然后题解说进一步分析(?)可以证明在 n = 60 n=60 n=60 时状态数不超过 50000 50000 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快,我直接疑惑了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值