P4921 [MtOI2018]情侣?给我烧了!/P4931 [MtOI2018]情侣?给我烧了!(加强版)
两道好题做了一上午,可见我的组合数学有多差,只会暴力拆式子,于是加强版只好看题解了。。总结一下于是有了这篇题解。
普通版
最 \(naive\) 的想法就是:钦定 \(k\) 对情侣,给他们安排座位,两个人可以互换位置,其余人是一个变相的错排:\(C_n^kA_n^k2^kf(n-k)\) ,\(f(k)\) 表示 \(k\) 对人坐 \(2k\) 个位置不配对的情况数。我发现这个 \(f(k)\) 我根本不会求就放弃了这个思路。
之前没见过二项式反演,所以没什么感觉。现在一上来就设俩式子:
\(f(k)\) 表示至少 \(k\) 对情侣和睦
\(g(k)\) 表示恰好 \(k\) 对情侣和睦
我们要求的是 \(g(i),i=0,1,\cdots,n\)
显然 \(f(k)=\sum_{i=k}^{n}\binom{n}{k}g(i)\) 。
由二项式反演得 \(g(k)=\sum_{i=k}^{n}(-1)^{i-k}\binom{n}{k}f(i)\)
考虑把 \(f(k)\) 换一种表示(之前设这两个式子的原因其实是因为看到 \(f(i)\) 好算):
首先钦定 \(k\) 对和睦的情侣,然后给他们挑 \(k\) 个位置,每一对情侣可以互换位置,其余人随便排(这里是“至少”的体现,有可能大于 \(k\) ,但是不可能小于 \(k\))
把上面的文字写成式子就是 \(f(k)=C_n^kA_n^k2^k(2n-2k)!\)
\(f\) 可以一遍 \(O(n)\) 扫出来,但是 \(g\) 呢?它要 \(O(n^2)\) 。怎么办?想了一会感觉没啥别的想法就开始大力拆式子:
\(\sum\) 后面那个东西对于每一个 \(n-k\) 是固定的,只有 \(O(n)\) 种,可以 \(O(n^2)\) 暴力预处理
然后就做完了。
//Orz cyn2006
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef double db;
#define mkp(x,y) make_pair(x,y)
#define fi first
#define se second
#define pb(x) push_back(x)
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=0;ch=getchar();}
while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
return f?x:-x;
}
#define N 2005
#define mod 998244353
int n,f[N],g[N],fac[N],ifc[N],pw2[N];
int qpow(int n,int k){int res=1;for(;k;k>>=1,n=1ll*n*n%mod)if(k&1)res=1ll*n*res%mod;return res;}
void init(){
int up=2000;
fac[0]=1,pw2[0]=1;
for(int i=1;i<=up;++i)fac[i]=1ll*fac[i-1]*i%mod,pw2[i]=(pw2[i-1]<<1)%mod;
ifc[up]=qpow(fac[up],mod-2);
for(int i=up-1;i>=0;--i)ifc[i]=1ll*ifc[i+1]*(i+1)%mod;
up=1000;
for(int i=0;i<=up;++i){
for(int j=0;j<=i;++j)
f[i]=(f[i]+1ll*(j&1?-1:1)*pw2[j]*ifc[j]%mod*fac[2*i-2*j]%mod*ifc[i-j]%mod*ifc[i-j]%mod+mod)%mod;
}
}
int A(int n,int m){return 1ll*fac[n]*ifc[n-m]%mod;}
int C(int n,int m){return 1ll*fac[n]*ifc[n-m]%mod*ifc[m]%mod;}
signed main(){
init();
for(int T=read();T;--T){
n=read();
for(int i=0;i<=n;++i)printf("%lld\n",1ll*pw2[i]*fac[n]%mod*fac[n]%mod*ifc[i]%mod*f[n-i]%mod);
}
}
加强版
原本以为就是简单版加一个多项式就好了的,那个式子 \(\sum\) 后面那部分显然可以 \(NTT\) 卷起来。
当我看到 \(n\le 5\times10^6\) 之后意识到事情有些不对劲。
看了题解发现神仙出题人把普通版里最开始写的那个式子的 \(f\) 推出来了!!!
技不如人啊,我被吊打了/kk
\(C_n^kA_n^k2^kf(n-k)\)
考虑怎么求 \(f\)
考虑分类讨论一下:
选两个人,ta们不是配偶,这样有 \(2k*(2k-2)\) 种情况,然后考虑ta们配偶的情况
- 如果强制让ta们配对,相当于在剩下 \(k-1\) 个位置选一个给ta们,同时ta们还可以交换位置,是 \(2(k-1)f(k-2)\)
- 如果强制让ta们分开,相当于少了俩人,剩下的人又是一个错排,为 \(f(k-1)\)
完全不知道第二种情况是怎么看出来的,出题人太神仙了。
边界 \(f(0)=1,f(1)=0\)
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef double db;
#define mkp(x,y) make_pair(x,y)
#define fi first
#define se second
#define pb(x) push_back(x)
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=0;ch=getchar();}
while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
return f?x:-x;
}
#define mod 998244353
#define N 5000005
int T,n,k;
int pw2[N],fac[N],ifc[N],f[N];
int qpow(int n,int k){int res=1;for(;k;k>>=1,n=1ll*n*n%mod)if(k&1)res=1ll*n*res%mod;return res;}
void init(){
int up=N-5;
pw2[0]=fac[0]=1;
for(int i=1;i<=up;++i)fac[i]=1ll*fac[i-1]*i%mod,pw2[i]=(pw2[i-1]<<1)%mod;
ifc[up]=qpow(fac[up],mod-2);
for(int i=up-1;i>=0;--i)ifc[i]=1ll*ifc[i+1]*(i+1)%mod;
f[0]=1,f[1]=0;
for(int i=2;i<=up;++i)f[i]=4ll*i*(i-1)%mod*(2ll*(i-1)*f[i-2]%mod+f[i-1])%mod;
}
int A(int n,int m){return 1ll*fac[n]*ifc[n-m]%mod;}
int C(int n,int m){return 1ll*fac[n]*ifc[n-m]%mod*ifc[m]%mod;}
signed main(){
init();
for(int T=read();T;--T)
n=read(),k=read(),printf("%lld\n",1ll*A(n,k)*C(n,k)%mod*pw2[k]%mod*f[n-k]%mod);
}