NOIP2023模拟9联测30 华二

题目大意

有一个长度为nnn的数列a1,a2,…,ana_1,a_2,\dots,a_na1,a2,,an,其中1≤ai≤91\leq a_i\leq 91ai9。对于相邻两项的aia_iaiai+1a_{i+1}ai+1,满足gcd⁡(ai,ai+1)=1\gcd(a_i,a_{i+1})=1gcd(ai,ai+1)=1时,你可以通过一次操作交换aia_iaiai+1(1≤i<n)a_{i+1}(1\leq i<n)ai+1(1i<n)

求可以通过这样的操作获得多少个序列(包括原序列)。输出答案模998244353998244353998244353后的值。

2≤n≤105,1≤ai≤92\leq n\leq 10^5,1\leq a_i\leq 92n105,1ai9


题解

对于ai=1/5/7a_i=1/5/7ai=1/5/7时,这些数可以放在任意位置(注意交换两个相同的数相当于不交换),我们可以将其放在最后处理。下面只考虑不是1,5,71,5,71,5,7aia_iai

然后,我们将剩下的数分成222的倍数和333的倍数:

  • 2,4,6,82,4,6,82,4,6,8
  • 3,6,93,6,93,6,9

注意任意两个在[1,9][1,9][1,9]中不相同的数的gcd⁡\gcdgcd如果不为111,则一定能被222333整除。

也就是说,在序列中,上面两类数的相对顺序是不变的。而666和其他数都不能交换,也就是说666的位置已经固定了。

对于两个666之间的数,这些数中222的倍数和333的倍数这两类数没有交集。设有xxx个数是222的倍数,有yyy个数是333的倍数,那么就相当于要在这一段中选择xxx个位置放222的倍数,剩下的位置放333的倍数,方案数为(x+yx)\binom{x+y}{x}(xx+y)。把每一段的(x+yx)\binom{x+y}{x}(xx+y)相乘即可。

下面考虑值为1/5/71/5/71/5/7aia_iai的贡献。设111的个数为w1w_1w1555的个数为w2w_2w2777的个数为w3w_3w3,上面得到的答案为nownownow,则答案为:

ans=(nw1+w2+w3)(w1+w2+w3w1)(w2+w3w2)×nowans=\binom{n}{w_1+w_2+w_3}\binom{w_1+w_2+w_3}{w_1}\binom{w_2+w_3}{w_2}\times nowans=(w1+w2+w3n)(w1w1+w2+w3)(w2w2+w3)×now

也就是先在nnn个位置中选出w+w2+w3w_+w_2+w_3w+w2+w3个位置放1,5,71,5,71,5,7,并在这些位置中分配1,5,71,5,71,5,7各自的位置,再乘上其他数的分配方案。

时间复杂度为O(n)O(n)O(n)

code

#include<bits/stdc++.h>
using namespace std;
const int N=100000;
const long long mod=998244353;
int n,t1=0,w1=0,w2=0,w3=0,a[N+5],t[N+5];
long long ans=1,jc[N+5],ny[N+5];
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;
}
long long C(int x,int y){
	return jc[x]*ny[y]%mod*ny[x-y]%mod;
}
long long solve(int l,int r){
	int v2=0,v3=0;
	for(int i=l;i<=r;i++){
		if(a[i]%2==0) ++v2;
		else if(a[i]%3==0) ++v3;
	}
	return C(v2+v3,v2);
}
int main()
{
//	freopen("b.in","r",stdin);
//	freopen("b.out","w",stdout);
	init();
	scanf("%d",&n);
	t[++t1]=0;
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		if(a[i]==1) ++w1;
		else if(a[i]==5) ++w2;
		else if(a[i]==7) ++w3;
		else if(a[i]==6) t[++t1]=i;
	}
	t[++t1]=n+1;
	for(int i=1;i<t1;i++){
		ans=ans*solve(t[i]+1,t[i+1]-1)%mod;
	}
	ans=ans*C(n,w1+w2+w3)%mod;
	ans=ans*C(w1+w2+w3,w1)%mod*C(w2+w3,w2)%mod;
	printf("%lld",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值