Wannafly挑战赛20-D:挑选队友 (DP+NTT优化)

链接:https://www.nowcoder.com/acm/contest/133/D

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld
题目描述
Applese打开了 m m 个QQ群,向群友们发出了组队的邀请。作为网红选手,Applese得到了n位选手的反馈,每位选手只会在一个群给Applese反馈
现在,Applese要挑选其中的 k k 名选手组队比赛,为了维持和各个群的良好关系,每个群中都应有至少一名选手成为Applese的队友(数据保证每个群都有选手给Applese反馈)
Applese想知道,他有多少种挑选队友的方案
输入描述:
输入包括两行
第一行包括三个数n,m,k表示共有 n n 位选手,m个群,需要有 k k 名选手被选择
第二行包括m个数,第 i i 个数表示第i个群有 si s i 个选手
n100000,mkn n ≤ 100000 , m ≤ k ≤ n

输出描述:
输出包括一行
第一行输出方案数
由于输出可能比较大,你只需要输出在模 998244353 998244353 意义下的答案
示例 1 1
输入
5 3 3 4
1 1 2 2 2
输出
4

思路: d[i][j] d [ i ] [ j ] 表示前 i i 个组选j个选手,且每个组至少选了一个选手的方案数。
d[i][j]+=sih=1d[i1][jh]Chsi d [ i ] [ j ] + = ∑ h = 1 s i d [ i − 1 ] [ j − h ] ∗ C s i h
于是利用卷积性质,用NTT优化转移即可。

#include<bits/stdc++.h>
using namespace std;
const int MAX=1e5+10;
const int MOD=998244353;//MOD=119*2^23+1 G=3;
const double PI=acos(-1.0);
typedef long long ll;
vector<ll>A[MAX];
ll POW(ll x,ll n)
{
    ll res=1;
    x%=MOD;
    while(n)
    {
        if(n&1)res=res*x%MOD;
        x=x*x%MOD;
        n/=2;
    }
    return res;
}
ll rev(ll x,ll len)
{
    ll ret=0;
    for(ll i=0;(1<<i)<len;i++)
    {
        ret<<=1;
        if((1<<i)&x)ret|=1;
    }
    return ret;
}
void NTT(vector<ll>& p,int len,int DFT)
{
    for(int i=0;i<len;i++)
    {
        int x=rev(i,len);
        if(i<x)swap(p[i],p[x]);
    }
    for(int i=1;i<len;i*=2)
    {
        ll wn=POW(3,(MOD-1)/(2*i));
        if(DFT==-1)wn=POW(wn,MOD-2);
        for(int j=0;j<len;j+=i*2)
        {
            ll w=1;
            for(int k=0;k<i;k++)
            {
                ll x=p[j+k];
                ll y=w*p[j+k+i]%MOD;
                p[j+k]=(x+y)%MOD;
                p[j+k+i]=(x-y+MOD)%MOD;
                w=w*wn%MOD;
            }
        }
    }
    if(DFT==-1)for(int i=0,x=POW(len,MOD-2);i<len;i++)p[i]=p[i]*x%MOD;
}
void mul(vector<ll>& a,vector<ll> b)
{
    int n=a.size();
    int m=b.size();
    int len=1;
    while(len<(n+m))len*=2;
    a.resize(len);
    b.resize(len);
    NTT(a,len,1);
    NTT(b,len,1);
    for(int i=0;i<len;i++)a[i]=a[i]*b[i]%MOD;
    NTT(a,len,-1);
    a.resize(n+m);
}
ll fac[MAX],inv[MAX];
queue<int>q;
int main()
{
    fac[0]=inv[0]=1;
    for(int i=1;i<=100000;i++)fac[i]=fac[i-1]*i%MOD;
    for(int i=1;i<=100000;i++)inv[i]=POW(fac[i],MOD-2);
    int n,m,k;
    cin>>n>>m>>k;
    for(int i=1;i<=m;i++)
    {
        int x;
        scanf("%d",&x);
        A[i].resize(x);
        for(int j=1;j<=x;j++)A[i][j-1]=fac[x]*inv[x-j]%MOD*inv[j]%MOD;
        q.push(i);
    }
    while(q.size()>1)
    {
        int x=q.front();q.pop();
        int y=q.front();q.pop();
        mul(A[x],A[y]);
        q.push(x);
    }
    printf("%lld\n",A[q.front()][k-m]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值