ABC310F Make 10 Again

ABC310F Make 10 Again

[ABC310F] Make 10 Again

题目大意

你有 n n n个骰子,第 i i i个骰子能等概率掷出 1 ∼ A i 1\sim A_i 1Ai的点数。

在同时掷出 n n n个骰子后,求满足下面条件的概率:

  • 能够在这些骰子中选若干个骰子,使得其点数和为 10 10 10

输出概率模 998244353 998244353 998244353后的值。

1 ≤ n ≤ 100 , 1 ≤ A i ≤ 1 0 6 1\leq n\leq 100,1\leq A_i\leq 10^6 1n100,1Ai106


题解

A i > 10 A_i>10 Ai>10时,大于 10 10 10的点数对凑 10 10 10没有用,所以我们记录点数小于等于 10 10 10的骰子的状态。

f i , s f_{i,s} fi,s表示掷出了前 i i i个骰子,当前状态为 s s s的情况, s s s中存储了 1 ∼ 10 1\sim 10 110中每个数的个数。但是,如果这样的话,状态数为 10 0 10 100^{10} 10010,显然不行。不过,我们发现,如果有 11 11 11 1 1 1,我们只需要 10 10 10个,多出的一个不需要;如果有 6 6 6 2 2 2,我们只需要 5 5 5个,多出的一个也不需要。那么,对于 1 ≤ i ≤ 10 1\leq i\leq 10 1i10,我们最多只需要 ⌊ n i ⌋ \lfloor \dfrac ni\rfloor in i i i,因为如果需要超过 ⌊ n i ⌋ \lfloor \dfrac ni\rfloor in i i i,那么凑成的数一定是大于 10 10 10的。

于是,我们可以将超过 ⌊ n i ⌋ \lfloor \dfrac ni\rfloor in i i i忽略。

那么,总状态数 T = ∏ i = 1 10 ( ⌊ n i ⌋ + 1 ) T=\prod\limits_{i=1}^{10}(\lfloor\dfrac ni\rfloor+1) T=i=110(⌊in+1),其值为七万多。

当骰子掷出 1 ≤ k ≤ 10 1\leq k\leq 10 1k10的点数时,在 s s s的对应状态中求加上一个 k k k(如果 k k k的数量已经到顶了,则不用加)后的状态 t t t,转移为

f i , s + = f i − 1 , t × 1 A i f_{i,s}+=f_{i-1,t}\times \dfrac{1}{A_i} fi,s+=fi1,t×Ai1

可以将每个 s s s k k k对应的 t t t预处理出,以优化这部分状态转移。

当骰子掷出 k > 10 k>10 k>10的点数时(此时 A i > 10 A_i>10 Ai>10),这个骰子对凑 10 10 10没有作用,则转移为

f i , s + = f i − 1 , s × A i − 10 A i f_{i,s}+=f_{i-1,s}\times \dfrac{A_i-10}{A_i} fi,s+=fi1,s×AiAi10

最后,用背包判断每个状态 s s s能否能凑成 10 10 10(这里可以将背包数组改为一个二进制数,用右移和或来实现转移),再将能凑成 10 10 10的状态 s s s f n , s f_{n,s} fn,s统计在答案中即可。

时间复杂度为 O ( 10 n T ) O(10nT) O(10nT)

code

#include<bits/stdc++.h>
using namespace std;
const int N=1000000;
const long long mod=998244353;
int n,tot,bg,a[15],v[105],nxt[100005][11],to[11][6][4][3][3][1<<6];
long long ans=0,jc[1000005],ny[1000005],f[105][100005];
struct node{
	int v1,v2,v3,v4,v5,z;
}w[100005];
long long mi(long long t,long long v){
	if(!v) return 1;
	long long re=mi(t,v/2);
	re=re*re%mod;
	if(v&1) re=re*t%mod;
	return re;
}
void init(){
	jc[0]=1;
	for(int i=1;i<=N;i++) jc[i]=jc[i-1]*i%mod;
	ny[N]=mi(jc[N],mod-2);
	for(int i=N-1;i>=0;i--) ny[i]=ny[i+1]*(i+1)%mod;
	for(int i=1;i<=N;i++) ny[i]=ny[i]*jc[i-1]%mod;
}
void dd(){
	int z=(a[6]<<4)|(a[7]<<3)|(a[8]<<2)|(a[9]<<1)|a[10];
	to[a[1]][a[2]][a[3]][a[4]][a[5]][z]=++tot;
	w[tot]=(node){a[1],a[2],a[3],a[4],a[5],z};
}
void dfs(int t){
	for(int i=0;i<=10/t;i++){
		a[t]=i;
		if(t<10) dfs(t+1);
		else dd();
	}
}
int pt(int s,int p){
	node t=w[s];
	if(p==1) t.v1=min(t.v1+1,10);
	else if(p==2) t.v2=min(t.v2+1,5);
	else if(p==3) t.v3=min(t.v3+1,3);
	else if(p==4) t.v4=min(t.v4+1,2);
	else if(p==5) t.v5=min(t.v5+1,2);
	else t.z|=(1<<10-p);
	return to[t.v1][t.v2][t.v3][t.v4][t.v5][t.z];
}
bool pd(int j){
	bg=1;
	for(int i=1;i<=w[j].v1;i++) bg|=(bg<<1);
	for(int i=1;i<=w[j].v2;i++) bg|=(bg<<2);
	for(int i=1;i<=w[j].v3;i++) bg|=(bg<<3);
	for(int i=1;i<=w[j].v4;i++) bg|=(bg<<4);
	for(int i=1;i<=w[j].v5;i++) bg|=(bg<<5);
	if(bg&(1<<10)) return 1;
	for(int i=6;i<=10;i++){
		if((w[j].z&(1<<10-i))&&(bg&(1<<10-i))) return 1;
	}
	return 0;
}
int main()
{
	init();
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&v[i]);
	}
	dfs(1);
	f[0][1]=1;
	for(int j=1;j<=tot;j++){
		for(int k=1;k<=10;k++){
			nxt[j][k]=pt(j,k);
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=tot;j++){
			if(!f[i-1][j]) continue;
			for(int k=1;k<=min(10,v[i]);k++){
				f[i][nxt[j][k]]=(f[i][nxt[j][k]]+f[i-1][j]*ny[v[i]])%mod;
			}
			if(v[i]>10){
				f[i][j]=(f[i][j]+f[i-1][j]*ny[v[i]]%mod*(v[i]-10))%mod;
			}
		}
	}
	for(int j=1;j<=tot;j++){
		if(pd(j)) ans=(ans+f[n][j])%mod;
	}
	printf("%lld",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值