Tyvj4876:骰子游戏 ((FFT/NTT)+倍增+DP)

20 篇文章 0 订阅
14 篇文章 0 订阅

题目传送门:http://tyvj.cn/p/4879


题目分析:其实这题我并没有AC(因为我没写过NTT),只是觉得这还是道不错的DP,就记录一下。骗访问量
读完题之后可以发现第一段话是废话,令x减去小A手中y点数的个数,问题就变成了:有n个人,每个人有m个骰子,所有点数为y的骰子的个数大于等于x的方案有多少种?考虑到一个人手中的骰子为{1,1,2}和{1,2,1}视作同一种方案,不妨先预处理出一个人手中点数为y的骰子数为i的方案数g[i]。由于骰子的具体点数并没有卵用,我们可以等价地将y看成6。根据DP的常见套路,我们规定每个人手中的骰子从左到右点数单调不降,记h[i][j]表示考虑到第i个骰子,其点数为j的方案数,则有: h[i][j]=jk=1f[i1][k] ,而g[i]为h[m-i][1~5]的和。
然后就是维护一个f[i][j]表示考虑完前i个人,点数为y的骰子数有j个的方案数,如果一个一个人去考虑,那么 f[i][j]=min(j,m)k=0f[i1][jk]g[k] ,时间复杂度 O(n2m2) 。但很明显可以用倍增来做,将i的意义改为考虑了 2i 个人而不是前i个人,然后用FFT或NTT加速卷积,时间复杂度 O(log(n)nmlog(nm))
(然而我是暴力卷积的,因为我虽然看过FFT的资料,但还没有系统地刷过题,计划会在NOIP之后学吧,好像整个机房就我还没写过FFT QAQ。如果想要AC代码,可以看神犇tututu的题解


CODE(70pt):

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=405;
const int maxl=70;
const long long M=998244353;
typedef long long LL;

LL h[maxn][5];
LL g[maxn];
LL f[maxl][maxn*maxn];
int cur=-1;
int n,m,x,y;

void Work(int a)
{
    if (!a)
    {
        ++cur;
        f[cur][0]=1;
        return;
    }
    int b=a>>1;
    Work(b);
    ++cur;
    b=b*m;
    for (int i=0; i<=b; i++)
        for (int j=0; j<=b; j++)
        {
            f[cur][i+j]+=(f[cur-1][i]*f[cur-1][j]%M);
            if (f[cur][i+j]>=M) f[cur][i+j]-=M;
        }
    if (a&1)
    {
        b<<=1;
        ++cur;
        for (int i=0; i<=b; i++)
            for (int j=0; j<=m; j++)
            {
                f[cur][i+j]+=(f[cur-1][i]*g[j]%M);
                if (f[cur][i+j]>=M) f[cur][i+j]-=M;
            }
    }
}

int main()
{
    freopen("game.in","r",stdin);
    freopen("game.out","w",stdout);

    scanf("%d%d%d%d",&n,&m,&x,&y);
    for (int i=1; i<=m; i++)
    {
        int a;
        scanf("%d",&a);
        if (a==y) x--;
    }

    for (int i=0; i<5; i++) h[1][i]=1;
    for (int i=2; i<=m; i++)
        for (int j=0; j<5; j++)
            for (int k=0; k<=j; k++)
            {
                h[i][j]+=h[i-1][k];
                if (h[i][j]>=M) h[i][j]-=M;
            }
    for (int i=0; i<m; i++)
        for (int j=0; j<5; j++)
        {
            g[i]+=h[m-i][j];
            if (g[i]>=M) g[i]-=M;
        }
    g[m]=1;

    Work(n);
    LL sum=0;
    for (int i=max(0,x); i<=n*m; i++)
    {
        sum+=f[cur][i];
        if (sum>=M) sum-=M;
    }
    printf("%lld\n",sum);

    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值