AtCoder Beginner Contest 217 G Groups (排列组合)

原题链接

题意

  • 给你 N N N个整数,标号为 1 1 1~ N N N
  • 要求分成无序非空的 k k k
  • M M M取模相同的数字不能分在同一组
  • 1 ≤ k ≤ n 1 \le k \le n 1kn都求一次答案
  • 2 ≤ N ≤ 5000 , 2 ≤ M ≤ N 2 \le N \le 5000,2 \le M \le N 2N5000,2MN

思路

  • 根据整数对 M M M的取模值会分成 M M M种数,这 M M M种数的个数最多只会有两种 ⌊ n m ⌋ \left \lfloor \frac{n}{m}\right \rfloor mn ⌊ n m ⌋ + 1 \left \lfloor \frac{n}{m}\right \rfloor+1 mn+1
  • 直接考虑非常困难,我们先考虑分成有序可为空的 k k k组的答案
  • 设第 i i i种数的个数为 c n t i cnt_i cnti,放置第 i i i种数的方案数即为 A k c n t i A_k^{cnt_i} Akcnti
  • 因此 f k = ∏ i = 0 i = M − 1 A k c n t i f_k=\prod_{i=0}^{i=M-1}A_k^{cnt_i} fk=i=0i=M1Akcnti
  • 然后考虑有序非空 g k = f k − ∑ i = 0 i = k − 1 C k i ∗ f i g_k=f_k-\sum_{i=0}^{i=k-1}C_k^i*f_i gk=fki=0i=k1Ckifi(把非空组数不到 k k k的减掉)
  • 最后在考虑本题,只需要将有序的方案数转换成无序,答案其实就是 g k k ! \frac{g_k}{k!} k!gk

代码

  • 时间复杂度 O ( n 2 ) O(n^2) O(n2)
#include <bits/stdc++.h>

using namespace std;




#define ls o<<1
#define rs o<<1|1
#define fi first
#define se second
typedef long long ll;
typedef pair<int,int> pii;
const int maxn=2e5+10;
const int mod=998244353;
const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;
const double PI=acos(-1.0);


ll g[maxn],f[maxn];
ll fac[maxn],inv[maxn];
ll cal1(ll n,ll m){
    if(m>n) return 1;
    return fac[n]*inv[n-m]%mod;
}
ll kpow(ll a,ll k){
    if(!a) return a;
    ll res=1;
    while(k){
        if(k&1) res=res*a%mod;
        a=a*a%mod;
        k>>=1;
    }
    return res;
}
void init(){
    fac[0]=inv[0]=1;
    for(int i=1;i<=5000;i++){
        fac[i]=fac[i-1]*i%mod;
        inv[i]=kpow(fac[i],mod-2);
    }
}
ll cal2(ll n,ll m){
    return fac[n]*inv[n-m]%mod*inv[m]%mod;
}
void solve(){
    int n,m;
    init();
    scanf("%d%d",&n,&m);
    ll cnt1=m-n%m,cnt2=n%m,index1=n/m,index2=n/m+1;
    int mi=(n+m-1)/m;
    for(int i=1;i<=n;i++){
        if(i<mi) {
            f[i]=0;puts("0");
            continue;
        }
        f[i]=kpow(cal1(i,index1),cnt1)*kpow(cal1(i,index2),cnt2)%mod;
        for(int j=1;j<=i-1;j++){
            f[i]=(f[i]-cal2(i,j)*f[j]%mod+mod)%mod;
        }
        if(i)
        printf("%lld\n",f[i]*inv[i]%mod);
    }

}
int main(){
    // clock_t t1=clock();
	#ifndef ONLINE_JUDGE
       freopen("in.in", "r", stdin);
       freopen("out.out","w",stdout);
    #endif 
	// int T;scanf("%d",&T);while(T--)
	solve();
// end:
    // cerr<<"Time used "<<clock()-t1<<" ms"<<endl;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值