[游记&题解]2019暑假中山纪中集训day13

中山纪中NOIP2019提高组模拟赛

A组 day13 solution

Date:2019.8.13

ProbelmABC
题目名称Count普及组提高组
Difficulty220027002800
FinishedYes--

简单游记

day 12

AM
打开题目,看到两个 998244353 998244353 998244353,一个 1000000007 1000000007 1000000007
心情简单.jpg…
然后想T1…
发现就是求 ∑ b i = s ∑bi=s bi=s b i ∈ [ 1 , m − 1 ] bi∈[1,m-1] bi[1,m1]的方案数…
然后首先想到了一个暴力dp,优化一下,70分就到手了.
然后发现没有限制就是一个组合数,考虑容斥.
以为容斥系数很难找,就直接跳过了。
[之前只做过一道毒瘤容斥题,然后之后看到容斥就觉得是神仙题…]
然后是T2…
观察前两个样例,发现一个是 2 ( n − 1 ) 2 2^{(n-1)^2} 2(n1)2,一个是 2 ( n − 1 ) 2 × n ! 2^{(n-1)^2}\times n! 2(n1)2×n!分析一下发现它就是加负数的方案数乘上放一个数的方案数。
发现 x x x都全部给出了,感觉有鬼,就把 x x x分解了一下质因数,发现幂次都不超过2…发现会做幂次全部为1的情况…30分就到手了…[前两个懒得写暴力…]
接着观察第三个样例,然而并没有什么发现…
然后是T3…
找了一下规律,发现一个数列对应一个max数列.
然后搞了个 n 3 n^3 n3的dp…[后来发现题解说这是 n 2 n^2 n2的???]
还没打完,就只剩10分钟了…
然后发现OJ根本打不开,于是就放弃了交题的希望…
PM
下午发现A题的容斥系数就是 ( − 1 ) k (-1)^k (1)k
B题幂次为2的递推一下就行了…
C题挺神仙的,不过有时转成方格图走路的套路…

Count (count)

[题解]

算法标签:组合数学,容斥

Subtask 1

定义 f [ s ] [ k ] f[s][k] f[s][k],表示前 k k k个数的和为 s s s的方案数,直接转移即可。
复杂度: O ( n 2 k ) O(n^2k) O(n2k)

Subtask 2

由于限制只和所有数的和与每个数是否取模 m m m等于 0 0 0有关。
我们可以先构造出一个数列 b i b_i bi,使得 b i ∈ [ 1 , m ) b_i∈[1,m) bi[1,m),然后再在 b b b数列上加一定多的 m m m使得和为 n n n即可,后面的部分直接组合数解决。
因此我们就是要限制 ∑ i = 1 k b i = s \sum^{k}_{i=1}b_i=s i=1kbi=s s + p m = n s+pm=n s+pm=n,注意到 s ≤ ( m − 1 ) ∗ k s≤(m-1)*k s(m1)k,相邻两个合法的 s s s的差距为 m m m,因此 s s s的数量是 O ( k ) O(k) O(k)的。
问题转化为:统计 ∑ i = 1 k b i = s , b i ∈ [ 1 , m ) \sum^{k}_{i=1}b_i=s,b_i∈[1,m) i=1kbi=s,bi[1,m)的合法序列个数。
可以将其看为将 s s s1划分为 k k k个集合,每个集合大小不超过 m − 1 m-1 m1,一个显然的暴力做法:
S u b t a s k 1 Subtask 1 Subtask1一样,定义 f [ s ] [ k ] f[s][k] f[s][k]表示前 k k k个数的和为 s s s的方案数。
显然转移方程为 f [ s ] [ k ] = ∑ f [ s − p ] [ k − 1 ] ( 1 ≤ p ≤ m − 1 ) f[s][k]=\sum f[s-p][k-1](1≤p≤m-1) f[s][k]=f[sp][k1](1pm1)
复杂度: O ( m 2 k 3 ) O(m^2k^3) O(m2k3)

Subtask 3

考虑优化上述dp。
由于转移的决策点都在同一个连续区间,我们可以使用前缀和优化。
定义 g [ i ] [ k ] = ∑ j = 1 i − 1 f [ j ] [ k ] g[i][k]=\sum^{i-1}_{j=1} f[j][k] g[i][k]=j=1i1f[j][k]
那么转移可以写为 f [ s ] [ k ] = g [ s ] [ k ] − g [ s − m + 2 ] [ k ] f[s][k]=g[s][k]-g[s-m+2][k] f[s][k]=g[s][k]g[sm+2][k]
复杂度: O ( m k 3 ) O(mk^3) O(mk3)

Subtask 4

我们发现如果没有 b i ∈ [ 1 , m ) b_i∈[1,m) bi[1,m)的限制就是一个经典的插(线)板问题,可以直接用组合数计算。
考虑使用容斥解决。
具体的公式是这样的:
∑ i = 0 ( − 1 ) i C s − ( m − 1 ) i − 1 k − 1 ∗ C k i \sum_{i=0}(-1)^iC^{k-1}_{s-(m-1)i-1}*C^{i}_{k} i=0(1)iCs(m1)i1k1Cki
意思就是每次枚举有多少个数大于等于 m m m,将这些方案数乘上容斥系数即可。
至于容斥系数,可以学习容斥原理,容斥系数 by gzy_cjoier大佬
复杂度: O ( k 2 ) O(k^2) O(k2)

[实现]

#pragma GCC optimize(2)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
#define MAXN 10000000
#define MOD 998244353
#define LL long long
LL n;int m,k,ans;
int fac[MAXN+5],inv[MAXN+5];
int fst_pow(int a,int b){
	int res=1;
	while(b){
		if(b&1)res=(1LL*res*a)%MOD;
		a=(1LL*a*a)%MOD;
		b>>=1;
	}return res;
}
void prepare(){
	fac[0]=1;
	for(int i=1;i<=MAXN;i++)
	fac[i]=(1LL*fac[i-1]*i)%MOD;
	inv[MAXN]=fst_pow(fac[MAXN],MOD-2);
	for(int i=MAXN-1;i>=0;i--)
	inv[i]=(1LL*inv[i+1]*(i+1))%MOD;
}
int Comb(int a,int b){
	if(a>b)return 0;
	return ((1LL*inv[a]*inv[b-a])%MOD*fac[b]*1LL)%MOD;
}
int Comb2(int a,LL b){
	if(a>b)return 0;
	int ps=1;
	for(LL i=b-a+1;i<=b;i++)
	ps=(1LL*ps*(i%MOD))%MOD;
	return (1LL*ps*inv[a])%MOD;
}
int main()
{
	freopen("count.in","r",stdin);
	freopen("count.out","w",stdout);
	scanf("%lld%d%d",&n,&m,&k);
	prepare();
	for(int s=n%m*1LL;s<=(m-1)*k;s+=m)//k
	{
		int rm=s-1,f=1,cnt=0;
		LL res=0;
		while(rm>0){
			LL tmp=(1LL*Comb(k-1,rm)*Comb(cnt,k))%MOD;
			res=(res+f*tmp+MOD)%MOD;
			rm-=(m-1);
			f*=(-1);cnt++;
		}
		res=(res*Comb2(k-1,((n-s)/m)+k-1))%MOD;
		ans=(ans+res)%MOD;
	}
	printf("%d",ans);
}

普及组 (pj)

[题解]

算法标签:数学推导,递推
GuGu

[实现]

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cmath>
using namespace std;
#define LL long long
#define MAXN 5000000
#define MOD 998244353
LL x;
int p1,p2,T;
int f1[MAXN+5],f2[MAXN+5];
int read(){
	int x=0,F=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')F=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*F;
}
int fst_pow(int a,int b){
	int res=1;
	while(b){
		if(b&1)res=(1LL*res*a)%MOD;
		a=(1LL*a*a)%MOD;
		b>>=1;
	}return res;
}
int main()
{
	freopen("pj.in","r",stdin);
	freopen("pj.out","w",stdout);
	scanf("%lld%d",&x,&T);
	for(LL i=2;i*i<=x;i++)
	if(x%i==0){
		int cnt=0;
		while(x%i==0)x/=i,cnt++;
		if(cnt==2)p2++;
		else p1++;
	}
	if(x!=1)p1++;
	int inv2=fst_pow(2,MOD-2);
	f1[0]=1;f2[1]=1,f2[2]=3;
	for(int i=1;i<=MAXN;i++)
	f1[i]=1LL*f1[i-1]*i%MOD;
	for(int i=3;i<=MAXN;i++)
	f2[i]=(((1LL*i*i%MOD)*f2[i-1]%MOD)-((((1LL*inv2*i%MOD)*(1LL*(i-1)*(i-1)%MOD))%MOD)*f2[i-2]%MOD)+MOD)%MOD;
	//Fi=i*i*Fi-1-0.5*i*(i-1)*(i-1)*Fi-2
	while(T--){
		int n=read();
		printf("%d\n",(1LL*fst_pow(f1[n],p1)*fst_pow(f2[n],p2)%MOD)*fst_pow(2,1LL*(n-1)*(n-1)%(MOD-1))%MOD);
	}
}

提高组 (tg)

[题解]

算法标签:数学推导,组合数学,数形结合

GuGu

[实现]

#pragma GCC optimize(2)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cmath>
using namespace std;
#define LL long long
#define MAXN 20000000
#define MOD 1000000007
int T;
int fac[MAXN+5],inv[MAXN+5];
int read(){
	int x=0,F=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')F=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*F;
}
int fst_pow(int a,int b){
	int res=1;
	while(b){
		if(b&1)res=(1LL*res*a)%MOD;
		a=(1LL*a*a)%MOD;
		b>>=1;
	}return res;
}
void prepare(){
	fac[0]=1;
	for(int i=1;i<=MAXN;i++)
	fac[i]=(1LL*fac[i-1]*i)%MOD;
	inv[MAXN]=fst_pow(fac[MAXN],MOD-2);
	for(int i=MAXN-1;i>=0;i--)
	inv[i]=(1LL*inv[i+1]*(i+1))%MOD;
}
LL Comb(int a,int b){
	if(a>b)return 0;
	return ((1LL*inv[a]*inv[b-a])%MOD*fac[b]*1LL)%MOD;
}
int main()
{
	freopen("tg.in","r",stdin);
	freopen("tg.out","w",stdout);
	T=read();
	prepare();
	while(T--){
		int n=read(),a=read(),b=read();
		if(a<b)swap(a,b);
		LL L=(Comb(b-1,a+b-2)-Comb(b-2,a+b-2)+MOD)%MOD,R=(Comb(n-b,n-a+n-b)-Comb(n-a-1,n-a+n-b)+MOD)%MOD;
		printf("%lld\n",(1LL*L*R)%MOD);
	}
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值