中山纪中NOIP2019提高组模拟赛
A组 day13 solution
Date:2019.8.13
Probelm | A | B | C |
---|---|---|---|
题目名称 | Count | 普及组 | 提高组 |
Difficulty | 2200 | 2700 | 2800 |
Finished | Yes | - | - |
简单游记
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,m−1]的方案数…
然后首先想到了一个暴力dp,优化一下,70分就到手了.
然后发现没有限制就是一个组合数,考虑容斥.
以为容斥系数很难找,就直接跳过了。
[之前只做过一道毒瘤容斥题,然后之后看到容斥就觉得是神仙题…]
然后是T2…
观察前两个样例,发现一个是
2
(
n
−
1
)
2
2^{(n-1)^2}
2(n−1)2,一个是
2
(
n
−
1
)
2
×
n
!
2^{(n-1)^2}\times n!
2(n−1)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≤(m−1)∗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
s个1
划分为
k
k
k个集合,每个集合大小不超过
m
−
1
m-1
m−1,一个显然的暴力做法:
与
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[s−p][k−1](1≤p≤m−1)
复杂度:
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=1i−1f[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[s−m+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−(m−1)i−1k−1∗Cki
意思就是每次枚举有多少个数大于等于
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);
}
}