Date:2019.8.13
Probelm | A | B | C |
---|---|---|---|
题目名称 | Count | 普及组 | 提高组 |
Difficulty | 2200 | 2700 | 2800 |
Finished | Yes | - | - |
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题挺神仙的,不过有时转成方格图走路的套路…
算法标签:组合数学
,容斥
。
定义 f [ s ] [ k ] f[s][k] f[s][k],表示前 k k k个数的和为 s s s的方案数,直接转移即可。
复杂度: O ( n 2 k ) O(n^2k) O(n2k)
由于限制只和所有数的和与每个数是否取模 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)
考虑优化上述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)
我们发现如果没有 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
#include
#include
#include
#include
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);
}
算法标签:数学推导
,递推
GuGu
#include
#include
#include
#include
#include
#include
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);
}
}
算法标签:数学推导
,组合数学
,数形结合
GuGu
#pragma GCC optimize(2)
#include
#include
#include
#include
#include
#include
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);
}
}