多项式
cdq分治FFT
给定序列 ,求序列 。
其中 ,边界为 。
答案对 取模。
考虑递归计算计算到,接着计算后半部分贡献
namespace NTT{
static constexpr int P=998244353;
int rev[N],A[N],B[N],bit=0,tot,g=3,gi;
int qpow(int a,int b){
int ans=1;
for(;b;b>>=1){
if(b&1)ans=ans*a%P;
a=a*a%P;
}
return ans;
}
int inv(int n){
return qpow(n,P-2);
}
void ntt(int *a,int inv){
for(int i=0;i<tot;i++){
if(i<rev[i])swap(a[i],a[rev[i]]);
}
for(int mid=1;mid<tot;mid<<=1){
int g1=qpow(inv==1?g:gi,(P-1)/(mid*2));
for(int i=0;i<tot;i+=mid*2){
int gk=1;
for(int j=0;j<mid;j++,gk=gk*g1%P){
auto x=a[i+j],y=gk*a[i+j+mid]%P;
a[i+j]=(x+y)%P,a[i+j+mid]=(x-y+P)%P;
}
}
}
if(inv==-1){
int tmp=NTT::inv(tot);
for(int i=0;i<tot;i++)a[i]=a[i]*tmp%P;
}
}
void init(int n){
bit=0;
while((1<<bit)<n)bit++;
tot=1<<bit;
for(int i=0;i<tot;i++)rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
gi=inv(g);
}
void mul(int *a,int *b){
ntt(a,1),ntt(b,1);
for(int i=0;i<tot;i++)a[i]=a[i]*b[i]%P;
ntt(a,-1);
}
void cdq_ntt(int *a,int *b,int l,int r){
if(l==r)return;
int mid=l+r>>1,len=r-l+1;
cdq_ntt(a,b,l,mid);
init(len);
for(int i=l;i<=mid;i++)A[i-l]=a[i];
for(int i=1;i<=r-l;i++)B[i-1]=b[i];
mul(A,B);
for(int i=mid+1;i<=r;i++)(a[i]+=A[i-l-1])%=P;
for(int i=0;i<tot;i++)A[i]=B[i]=0;
cdq_ntt(a,b,mid+1,r);
}
}
int f[N],g[N];
void solve(){
int n;
cin>>n;
f[0]=1;
for(int i=1;i<n;i++)cin>>g[i];
NTT::cdq_ntt(f,g,0,n-1);
for(int i=0;i<n;i++)cout<<f[i]<<" \n"[i==n-1];
}板子集合
namespace Poly{
using poly=vector<int>;
vector<int> rev;
const int P=998244353;
int qpow(int a,int b){
int ans=1;
for(;b;b>>=1){
if(b&1)ans=ans*a%P;
a=a*a%P;
}
return ans;
}
int bit=0,tot,g=3,gi=qpow(g,P-2);
void ntt(poly &a,int inv){
for(int i=0;i<tot;i++){
if(i<rev[i])swap(a[i],a[rev[i]]);
}
for(int mid=1;mid<tot;mid<<=1){
int g1=qpow(inv==1?g:gi,(P-1)/(mid*2));
for(int i=0;i<tot;i+=mid*2){
int gk=1;
for(int j=0;j<mid;j++,gk=gk*g1%P){
auto x=a[i+j],y=gk*a[i+j+mid]%P;
a[i+j]=(x+y)%P,a[i+j+mid]=(x-y+P)%P;
}
}
}
if(inv==-1){
int invn=qpow(tot,P-2);
for(int i=0;i<tot;i++)a[i]=a[i]*invn%P;
}
}
void init(int n){
bit=0;
while((1<<bit)<n)bit++;
tot=1<<bit;
rev.resize(tot);
for(int i=0;i<tot;i++)rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
}
// 多项式乘法
poly mul(poly a,poly b){
int n=a.size(),m=b.size();
init(n+m+1);
a.resize(tot),b.resize(tot);
ntt(a,1),ntt(b,1);
for(int i=0;i<tot;i++)a[i]=a[i]*b[i]%P;
ntt(a,-1);
for(int i=n+m;i<tot;i++)a[i]=0;
return a;
}
// 多项式求逆
poly inv(const poly &f,int n){
if(n==1)return {qpow(f[0],P-2)};
auto g=inv(f,n+1>>1);
static vector<int> c;
init(2*n+1);
c.resize(tot);
g.resize(tot);
for(int i=0;i<n;i++)c[i]=f[i];
for(int i=n;i<tot;i++)c[i]=0;
ntt(c,1),ntt(g,1);
for(int i=0;i<tot;i++)g[i]=g[i]*(2-c[i]*g[i]%P+P)%P;
ntt(g,-1);
for(int i=n;i<tot;i++)g[i]=0;
return g;
}
// 多项式求导
poly der(const poly &f){
int n=f.size();
poly g(n-1);
for(int i=1;i<n;i++)g[i-1]=f[i]*i%P;
return g;
}
// 多项式求积分
poly inte(const poly &f){
int n=f.size();
poly g(n+1);
g[0]=0;
for(int i=1;i<=n;i++)g[i]=f[i-1]*qpow(i,P-2)%P;
return g;
}
// 多项式求值
int eval(const poly &f,int x){
int n=f.size(),ans=0;
for(int i=n-1;i>=0;i--)ans=(ans*x+f[i])%P;
return ans;
}
// 多项式翻转Ar(x)=x^n*A(1/x)
poly reverse(const poly &f,int n){
poly g(n);
for(int i=0;i<n;i++)g[i]=f[n-i-1];
return g;
}
// 多项式加法
poly add(const poly &f,const poly &g){
poly c(max(f.size(),g.size()));
for(int i=0;i<max(f.size(),g.size());i++){
if(i<f.size())c[i]=f[i];
if(i<g.size())c[i]=(c[i]+g[i])%P;
}
return c;
}
// 多项式减法
poly sub(const poly &f,const poly &g){
poly c(max(f.size(),g.size()));
for(int i=0;i<max(f.size(),g.size());i++){
if(i<f.size())c[i]=f[i];
if(i<g.size())c[i]=(c[i]-g[i]+P)%P;
}
return c;
}
// 多项式除法
pair<poly,poly> div(const poly &f,const poly &g){
int n=f.size(),m=g.size(),len=n-m+1;
if(n<m)return {{},f};
poly fr=reverse(f,n),gr=reverse(g,m);
gr.resize(len);
for(int i=len;i<m;i++)gr[i]=0;
gr=inv(gr,len);
poly q=mul(fr,gr);
q=reverse(q,len);
poly r=sub(f,mul(q,g));
return {q,r};
}
// cipolla
int cipolla(int x){
if(qpow(x,P-1>>1)==P-1)return -1;
while(1){
int a=rng()%P;
int w=(a*a%P-x+P)%P;
auto mul=[&](PII a,PII b){
return PII{(a.F*b.F%P+w*a.S%P*b.S%P)%P,(a.F*b.S%P+a.S*b.F%P)%P};
};
auto pow=[&](PII a,int b){
PII res{1,0};
for(;b;b>>=1){
if(b&1)res=mul(res,a);
a=mul(a,a);
}
return res.F;
};
if(qpow(w,P-1>>1)==P-1){
int res=pow(PII{a,1},P+1>>1);
return min(res,P-res);
}
}
}
// 多项式开根(a0!=1)
poly sqrt(const poly &f,int n){
if(n==1){
if(f[0]==1)return {1};
return {cipolla(f[0])};
}
auto g=sqrt(f,n+1>>1);
static vector<int> c;
static const int inv2=qpow(2,P-2);
init(2*n+1);
c.resize(tot);
g.resize(tot);
for(int i=0;i<n;i++)c[i]=f[i];
for(int i=n;i<tot;i++)c[i]=0;
auto ig=inv(g,n);
ntt(g,1),ntt(ig,1),ntt(c,1);
for(int i=0;i<tot;i++)g[i]=(g[i]*g[i]%P+c[i])%P*ig[i]%P*inv2%P;
ntt(g,-1);
for(int i=n;i<tot;i++)g[i]=0;
return g;
}
// 多项式对数ln
poly ln(const poly &f){
return inte(mul(der(f),inv(f,f.size())));
}
// 多项式指数exp
poly exp(const poly &f,int n){
if(n==1)return {1};
poly g=exp(f,n+1>>1);
g.resize(n);
poly lng=ln(g);
init(2*n+1);
g.resize(tot);
lng.resize(tot);
for(int i=0;i<n;i++)lng[i]=(f[i]-lng[i]+P)%P;
for(int i=n;i<tot;i++)lng[i]=g[i]=0;
lng[0]++;
ntt(lng,1);
ntt(g,1);
for(int i=0;i<tot;i++)g[i]=g[i]*lng[i]%P;
ntt(g,-1);
for(int i=n;i<tot;i++)g[i]=0;
return g;
}
// 多项式快速幂
poly qpow(const poly &f,int b){
int n=f.size();
poly res{1},a=f;
res[0]=1;
init(n<<1);
res.resize(tot);
a.resize(tot);
for(;b;b>>=1){
ntt(a,1);
if(b&1){
ntt(res,1);
for(int i=0;i<tot;i++)res[i]=res[i]*a[i]%P;
ntt(res,-1);
for(int i=n;i<tot;i++)res[i]=0;
}
for(int i=0;i<tot;i++)a[i]=a[i]*a[i]%P;
ntt(a,-1);
for(int i=n;i<tot;i++)a[i]=0;
}
return res;
}
// 多项式多点求值
vector<int> evals(const poly &f,vector<int> &x){
vector<poly> T;
int n=f.size(),m=x.size();
T.assign(m<<2,{});
function<void(int,int,int)> build=[&](int p,int l,int r){
if(l==r){
T[p]={(P-x[l])%P,1};
return;
}
int mid=l+r>>1;
build(p<<1,l,mid),build(p<<1|1,mid+1,r);
T[p]=mul(T[p<<1],T[p<<1|1]);
while(T[p].size()>1&&T[p].back()==0)T[p].pop_back();
};
vector<int> res(m);
function<void(int,int,int,poly)> solve=[&](int p,int l,int r,poly f){
if(r-l+1<=64){
for(int i=l;i<=r;i++){
res[i]=eval(f,x[i]);
}
return;
}
auto [q1,r1]=div(f,T[p<<1]);
auto [q2,r2]=div(f,T[p<<1|1]);
while(r1.size()>1&&r1.back()==0)r1.pop_back();
while(r2.size()>1&&r2.back()==0)r2.pop_back();
int mid=l+r>>1;
solve(p<<1,l,mid,r1);
solve(p<<1|1,mid+1,r,r2);
};
build(1,0,m-1);
if(f.size()>=T[1].size()){
auto [q,r]=div(f,T[1]);
solve(1,0,m-1,r);
}
else solve(1,0,m-1,f);
return res;
}
}modint板子
struct mint {
constexpr static int mod = 998244353;
int x;
inline mint(int o = 0) { x = o%mod; }
inline mint & operator = (int o) { return x = o%mod, *this; }
inline mint & operator += (mint o) { return (x += o.x) >= mod && (x -= mod), *this; }
inline mint & operator -= (mint o) { return (x -= o.x) < 0 && (x += mod), *this; }
inline mint & operator *= (mint o) { return x = (long long) x * o.x % mod, *this; }
inline mint& qpow(int b){
mint w = *this;
mint ret(1);
for(; b; b >>= 1, w *= w) if(b & 1) ret *= w;
return x = ret.x, *this;
}
inline mint & operator /= (mint o) { return *this *= (o .qpow( (mod - 2))); }
friend inline mint operator + (mint a, mint b) { return a += b; }
friend inline mint operator - (mint a, mint b) { return a -= b; }
friend inline mint operator * (mint a, mint b) { return a *= b; }
friend inline mint operator / (mint a, mint b) { return a /= b; }
friend ostream& operator<<(ostream& os, const mint& m) {
return os << m.x;
}
};拉格朗日插值
个数确定次多项式,
O(n^2)版本
int lagrange(int k,vector<int> &X,vector<int> &Y){
int ans=0;
int n=X.size();
for(int i=0;i<n;i++){
int fz=1,fm=1;
for(int j=0;j<n;j++){
if(i==j)continue;
fz=fz*(k-X[j])%mod;
fm=fm*(X[i]-X[j])%mod;
}
ans=(ans+Y[i]*fz%mod*qpow(fm,mod-2)%mod)%mod;
}
return (ans+mod)%mod;
}O(n)版本
- 如果取值连续,那么把换成,
- 记
- ,为奇数时分母取负
int lagrange(int k,vector<int> &X,vector<int> &Y,int n){
int ans=0;
vector<int> pre(n+10),suf(n+10),ifac(n+10);
pre[0]=(k-X[0])%mod,suf[n+1]=1;
for(int i=1;i<=n;i++)pre[i]=pre[i-1]*(k-X[i])%mod;
for(int i=n;i>=0;i--)suf[i]=suf[i+1]*(k-X[i])%mod;
ifac[0]=ifac[1]=1;
for(int i=2;i<=n;i++)ifac[i]=-1ll*mod/i*ifac[mod%i]%mod;
for(int i=2;i<=n;i++)ifac[i]=ifac[i]*ifac[i-1]%mod;
for(int i=0;i<=n;i++){
int tmp=Y[i]*(i==0?1:pre[i-1])%mod*suf[i+1]%mod;
tmp=tmp*ifac[i]%mod*ifac[n-i]%mod;
if(n-i&1)tmp=mod-tmp;
ans=(ans+tmp)%mod;
}
return (ans+mod)%mod;
}求
复杂度
void solve(){
int n,k;
cin>>n>>k;
vector<int> X(k+2),Y(k+2);
iota(X.begin(),X.end(),1);
Y[0]=1;
for(int i=1;i<=k+1;i++)Y[i]=(Y[i-1]+qpow(i+1,k))%mod;
cout<<lagrange(n,X,Y,k+1);
}重心拉格朗日插值法
- 每新加入一个点只需要计算即可,预处理仍然是,但是插入是的
多项式乘法逆
给定一个多项式 ,请求出一个多项式 , 满足 。系数对 取模。
牛顿迭代法 已知一个复合多项式 ,假设
泰勒公式在展开,
将套进去
poly inv(poly &f,int n){
if(n==1)return {qpow(f[0],P-2)};
auto g=inv(f,n+1>>1);
static vector<int> c;
init(2*n+1);
c.resize(tot);
g.resize(tot);
for(int i=0;i<n;i++)c[i]=f[i];
for(int i=n;i<tot;i++)c[i]=0;
ntt(c,1),ntt(g,1);
for(int i=0;i<tot;i++)g[i]=g[i]*(2-c[i]*g[i]%P+P)%P;
ntt(g,-1);
for(int i=n;i<tot;i++)g[i]=0;
return g;
}
void solve(){
int n;
cin>>n;
poly f(n+1);
for(int i=0;i<n;i++)cin>>f[i];
auto g=inv(f,n);
for(int i=0;i<n;i++)cout<<g[i]<<" ";
}多项式求导
poly der(poly &f){
int n=f.size();
poly g(n-1);
for(int i=1;i<n;i++)g[i-1]=f[i]*i%P;
return g;
}多项式求积分
poly inte(poly &f){
int n=f.size();
poly g(n+1);
g[0]=0;
for(int i=1;i<=n;i++)g[i]=f[i-1]*qpow(i,P-2)%P;
return g;
}多项式除法
给定一个 次多项式 和一个 次多项式 ,请求出多项式 , ,满足以下条件:
- 次数为 , 次数小于
考虑一种翻转操作,,即
最后
pair<poly,poly> div(const poly &f,const poly &g){
int n=f.size(),m=g.size(),len=n-m+1;
poly fr=reverse(f,n),gr=reverse(g,m);
gr.resize(len);
for(int i=len;i<m;i++)gr[i]=0;
gr=inv(gr,len);
poly q=mul(fr,gr,n,len);
q=reverse(q,len);
poly r=sub(f,mul(q,g,len,m));
return {q,r};
}
void solve(){
int n,m;
cin>>n>>m;
poly f(n+1),g(m+1);
for(int i=0;i<=n;i++)cin>>f[i];
for(int i=0;i<=m;i++)cin>>g[i];
auto [q,r]=div(f,g);
for(int i=0;i<=n-m;i++)cout<<q[i]<<" \n"[i==n-m];
for(int i=0;i<m;i++)cout<<r[i]<<" \n"[i==m-1];
}多项式开根(a0=1)
给定一个 次多项式 ,求一个在 意义下的多项式 ,使得 。若有多解,请取零次项系数较小的作为答案。
多项式的系数在 的意义下进行运算。
牛顿迭代法,构造式子
带入式子得
poly sqrt(const poly &f,int n){
if(n==1)return {1};
auto g=sqrt(f,n+1>>1);
static vector<int> c;
static const int inv2=qpow(2,P-2);
init(2*n+1);
c.resize(tot);
g.resize(tot);
for(int i=0;i<n;i++)c[i]=f[i];
for(int i=n;i<tot;i++)c[i]=0;
auto ig=inv(g,n);
ntt(g,1),ntt(ig,1),ntt(c,1);
for(int i=0;i<tot;i++)g[i]=(g[i]*g[i]%P+c[i])%P*ig[i]%P*inv2%P;
ntt(g,-1);
for(int i=n;i<tot;i++)g[i]=0;
return g;
}
void solve(){
int n;
cin>>n;
poly f(n+1);
for(int i=0;i<n;i++)cin>>f[i];
auto g=sqrt(f,n+1);
for(int i=0;i<n;i++)cout<<g[i]<<" ";
}多项式开根a0!=1
需要检验a0是否有二次剩余,即是否成立
int cipolla(int x){
if(qpow(x,P-1>>1)==P-1)return -1;
while(1){
int a=rng()%P;
int w=(a*a%P-x+P)%P;
auto mul=[&](PII a,PII b){
return PII{(a.F*b.F%P+w*a.S%P*b.S%P)%P,(a.F*b.S%P+a.S*b.F%P)%P};
};
auto pow=[&](PII a,int b){
PII res{1,0};
for(;b;b>>=1){
if(b&1)res=mul(res,a);
a=mul(a,a);
}
return res.F;
};
if(qpow(w,P-1>>1)==P-1){
int res=pow(PII{a,1},P+1>>1);
return min(res,P-res);
}
}
}
// 多项式开根(a0!=1)
poly sqrt(const poly &f,int n){
if(n==1){
if(f[0]==1)return {1};
return {cipolla(f[0])};
}
auto g=sqrt(f,n+1>>1);
static vector<int> c;
static const int inv2=qpow(2,P-2);
init(2*n+1);
c.resize(tot);
g.resize(tot);
for(int i=0;i<n;i++)c[i]=f[i];
for(int i=n;i<tot;i++)c[i]=0;
auto ig=inv(g,n);
ntt(g,1),ntt(ig,1),ntt(c,1);
for(int i=0;i<tot;i++)g[i]=(g[i]*g[i]%P+c[i])%P*ig[i]%P*inv2%P;
ntt(g,-1);
for(int i=n;i<tot;i++)g[i]=0;
return g;
}多项式对数函数(多项式 ln)(a_0=1)
给出 次多项式 ,求一个 下的多项式 ,满足 .
在 下进行,且
保证 .
令 ,
两边求导
求出来后再积回去
poly ln(const poly &f,int n){
return inte(mul(der(f),inv(f,n)));
}多项式指数函数(多项式 exp)(a_0=0)
给出 次多项式 ,求一个 下的多项式 ,满足 。系数对 取模。
保证 .
牛顿迭代法,,
poly exp(const poly &f,int n){
if(n==1)return {1};
poly g=exp(f,n+1>>1);
g.resize(n);
poly lng=ln(g);
init(2*n+1);
g.resize(tot);
lng.resize(tot);
for(int i=0;i<n;i++)lng[i]=(f[i]-lng[i]+P)%P;
for(int i=n;i<tot;i++)lng[i]=g[i]=0;
lng[0]++;
ntt(lng,1);
ntt(g,1);
for(int i=0;i<tot;i++)g[i]=g[i]*lng[i]%P;
ntt(g,-1);
for(int i=n;i<tot;i++)g[i]=0;
return g;
}多项式多点求值
// 多项式多点求值
vector<int> evals(const poly &f,vector<int> &x){
vector<poly> T;
int n=f.size(),m=x.size();
T.assign(m<<2,{});
function<void(int,int,int)> build=[&](int p,int l,int r){
if(l==r){
T[p]={(P-x[l])%P,1};
return;
}
int mid=l+r>>1;
build(p<<1,l,mid),build(p<<1|1,mid+1,r);
T[p]=mul(T[p<<1],T[p<<1|1]);
while(T[p].size()>1&&T[p].back()==0)T[p].pop_back();
};
vector<int> res(m);
function<void(int,int,int,poly)> solve=[&](int p,int l,int r,poly f){
if(r-l+1<=64){
for(int i=l;i<=r;i++){
res[i]=eval(f,x[i]);
}
return;
}
auto [q1,r1]=div(f,T[p<<1]);
auto [q2,r2]=div(f,T[p<<1|1]);
while(r1.size()>1&&r1.back()==0)r1.pop_back();
while(r2.size()>1&&r2.back()==0)r2.pop_back();
int mid=l+r>>1;
solve(p<<1,l,mid,r1);
solve(p<<1|1,mid+1,r,r2);
};
build(1,0,m-1);
if(f.size()>=T[1].size()){
auto [q,r]=div(f,T[1]);
solve(1,0,m-1,r);
}
else solve(1,0,m-1,f);
return res;
}
void solve(){
int n,m;
cin>>n>>m;
n++;
vector<int> f(n),a(m);
for(int i=0;i<n;i++)cin>>f[i];
for(int i=0;i<m;i++)cin>>a[i];
auto ans=evals(f,a);
for(int i=0;i<m;i++)cout<<ans[i]<<"\n";
}多项式快速幂(a_0=1)
- 首先对k直接取模,然后直接算,复杂度
poly qpow(const poly &f,int b){
int n=f.size();
poly res{1},a=f;
res[0]=1;
init(n<<1);
res.resize(tot);
a.resize(tot);
ntt(res,1),ntt(a,1);
for(;b;b>>=1){
if(b&1){
for(int i=0;i<tot;i++)res[i]=res[i]*a[i]%P;
ntt(res,-1);
for(int i=n;i<tot;i++)res[i]=0;
ntt(res,1);
}
for(int i=0;i<tot;i++)a[i]=a[i]*a[i]%P;
ntt(a,-1);
for(int i=n;i<tot;i++)a[i]=0;
ntt(a,1);
}
ntt(res,-1);
return res;
}- 都取对数,先算出右边,再exp回去
- 复杂度
poly qpow(const poly &f,int b){
poly lnf=ln(f);
for(int i=0;i<lnf.size();i++)lnf[i]=lnf[i]*b%P;
return exp(lnf,f.size());
}多项式复合逆
令 次多项式 。
给定 和 的各项系数,要求一个 次多项式 满足:
求 的各项系数对 取模的结果。
保证 ,。
生成函数
概念
常用公式
- 求的方法为,求的的系数
因为很容易想到
- 二项式展开:
- 负二项式展开:
- 广义形式:,因此
泰勒展开
普通生成函数(OCF)
- (几何级数)
- (自然数序列)
- (平方数序列)
- (二项式系数)
- (斐波那契数列)
- (调和数)
指数生成函数(EGF)
- (排列数)
- (循环排列数)
- (贝尔数集合划分)
有穷序列的生成函数
P10780 BZOJ3028 食物
题目描述
明明这次又要出去旅游了,和上次不同的是,他这次要去宇宙探险!我们暂且不讨论他有多么 NC,他又幻想了他应该带一些什么东西。理所当然的,你当然要帮他计算携带 件物品的方案数。
他这次又准备带一些受欢迎的食物,如:蜜桃多啦,鸡块啦,承德汉堡等等。
当然,他又有一些稀奇古怪的限制:
每种食物的限制如下:
- 承德汉堡:偶数个;
- 可乐: 个或 个;
- 鸡腿: 个, 个或 个;
- 蜜桃多:奇数个;
- 鸡块: 的倍数个;
- 包子: 个, 个, 个或 个;
- 土豆片炒肉:不超过一个;
- 面包: 的倍数个;
注意,这里我们懒得考虑明明对于带的食物该怎么搭配着吃,也认为每种食物都是以『个』为单位(反正是幻想嘛),只要总数加起来是 就算一种方案。因此,对于给出的 ,你需要计算出方案数,并对 取模。
提示
- 对于 的数据,但是 ;
- 对于所有数据,;
思路
- 承德汉堡的生成函数:
- 可乐为:
- 鸡腿为:
- 蜜桃为:
- 鸡块为:
- 包子为:
- 土豆片炒肉为:
- 面包为:
- 全部相乘得到
- 将式子展开,得到的 的系数即为方案数
- 答案即为
多重集组合数问题
有种物品,每种物品有个,问取个物品的组合数?
那么构造个生成函数,第个为
所有函数相乘,指数为的系数即为答案
abc248c dice_sum
给定三个整数N,M,K,求多少种序列:
- A长度为N
那么要求的是的前k项和,转化为求的项
实际上求的项
void solve(){
init();
int n,m,k;
cin>>n>>m>>k;
int ans=0;
for(int i=0;i*m<=k-n;i++){
int tmp=C(n,i)*C(k-i*m,k-n-i*m)%mod;
if(i&1)tmp*=-1;
ans=(ans+tmp+mod)%mod;
}
cout<<ans<<"\n";
}更无脑的方法是直接分治ntt,复杂度,如果是,那么可以快速幂+多项式求逆+ntt,复杂度(过于抽象没有实现)
指数生成函数
序列的指数生成函数为
序列的指数生成函数为
多重集排列数问题
有个物品,每种物品个,问个物品的排列数
那么答案就是所有的和,的系数
POJ3734 Blocks
长度的序列,四种颜色,红色和绿色数量必须为偶数,求方案数
红色和绿色:
蓝色黄色:
因为
因此系数为,系数为
牛客17316(挑选队友)(分治FFT)
共有位选手,个群,名选手被选择,问方案数
表示该群的人数,可以发现该群的的多项式系数为,个数卷积即可
使用分治fft
,复杂度为
void solve(){
int n,m,k;
cin>>n>>m>>k;
comb.init(n);
vector<int> s(n+1);
for(int i=1;i<=m;i++)cin>>s[i];
auto dfs=[&](auto &&dfs,int l,int r){
if(l==r){
poly f(s[l]+1);
for(int i=1;i<=s[l];i++)f[i]=comb.binom(s[l],i);
return f;
}
int mid=l+r>>1;
return mul(dfs(dfs,l,mid),dfs(dfs,mid+1,r));
};
auto res=dfs(dfs,1,m);
cout<<res[k]<<"\n";
}注意mul函数处的init(n+m+1)改成init(n+m),否则复杂度是错的
abc234f Reordering(指数型)
给定字符串,求他的所有排列能生成的子序列个数
26个字母,每个字母出现次数为个,该字母的生成函数为
所有26个字母相乘
答案为
void solve(){
string s;
cin>>s;
int n=s.size();
comb.init(n);
poly res{1};
vector<int> a(26);
for(auto c:s)a[c-'a']++;
for(int i=0;i<26;i++){
if(!a[i])continue;
poly tmp(a[i]+1,1);
for(int j=1;j<=a[i];j++)tmp[j]=comb.inv[j];
res.resize(n+1);
res=mul(res,tmp);
}
int ans=0;
for(int i=1;i<=n;i++)ans=(ans+comb.fac[i]*res[i])%mod;
cout<<ans<<"\n";
}CF632F
种物品,价值为,求拿走恰好种物品的所有可能价值,
定义表示选择了个物品,价值为的方案数,转移
换个思路,定义表示价值为的最小物品数
假设价值最小值为,那么所有物品,那么此时即使,可以添加价值为0的物品数,答案就是
另一种思路:
定义为价值的出现次数,
那么是一个卷积形式,NTT求解(不卡模数的情况下)
CF1096G
一个位数,每位可以是给出的个数码的一个数,可以有前导,输出前和后相等的方案数
设出现的数位集合为,那么生成函数,答案为
GYM102920H
三条水平线a,b,c,每个水平线有个点,高度相差1,求三点共线数量。
,构造生成函数,,是值的集合,答案就是中的系数,前提
做一遍FFT即可
HDU7328 Snake
有个长度为1的蛇,求集合个数(比如集合有{1->2},{2->1},{1,2},{2,1},最终有条蛇,每条蛇长度最多为
容斥做法:
考虑没有排列的因素,表示至少条蛇大于的方案
- 对于,选出个集合等于的,
- 剩余分成份,
考虑排序,全排列,但是集合不排
void solve(){
int n,m,k;
cin>>n>>m>>k;
vector<int> f(n+1);
for(int i=0;i<=m;i++){
f[i]=comb.binom(m,i)*comb.binom(n-i*k-1,m-1)%mod;
}
int ans=0;
for(int i=0;i<=m;i++){
if(i&1)ans=(ans+mod-f[i])%mod;
else ans=(ans+f[i])%mod;
}
cout<<ans*comb.fac[n]%mod*comb.inv[m]%mod<<"\n";
}生成函数做法:
每个集合的生成函数
答案就是,化简和abc248c一样
最后答案为
2017CCPC 杭州Marriage(容斥+生成函数)
有个家庭,每个家庭有个男孩和个女孩,,除了同家庭,其余男女配对,问方案数
考虑容斥,表示至少对同家庭的
设为当前家庭的贡献,设贡献对,方案为,生成函数为
那么所有生成函数相乘,
容斥一下计算答案即可
复杂度
分治NTT需要用优先队列启发式合并实现
void solve(){
int n;
cin>>n;
vector<int> a(n+1),b(n+1);
for(int i=1;i<=n;i++)cin>>a[i]>>b[i];
int s=accumulate(a.begin()+1,a.end(),0LL);
int t=0;
for(int i=1;i<=n;i++)t+=min(a[i],b[i]);
vector<poly> f(n+1);
for(int i=1;i<=n;i++){
int p=min(a[i],b[i]);
f[i].resize(p+1);
for(int j=0;j<=p;j++)f[i][j]=comb.binom(a[i],j)*comb.binom(b[i],j)%mod*comb.fac[j]%mod;
}
auto cmp=[&](int x,int y){
return f[x].size()>f[y].size();
};
priority_queue<int,vector<int>,decltype(cmp)> pq(cmp);
for(int i=1;i<=n;i++)pq.push(i);
while(pq.size()>1){
auto x=pq.top();pq.pop();
auto y=pq.top();pq.pop();
int z=f.size();
auto g=mul(f[x],f[y]);
g.resize(min((int)g.size(),t+1));
f.push_back(g);
pq.push(z);
}
auto F=f.back();
int ans=0;
for(int i=0;i<=t;i++){
int tmp=F[i]*comb.fac[s-i]%mod;
if(i&1)tmp=mod-tmp;
ans=(ans+tmp)%mod;
}
cout<<ans<<'\n';
}CCPC桂林B
求,执行次
将数组倒序,构造数组
每执行一次操作即
答案是
void solve(){
int _=0;
cin>>_;
for(int T=1;T<=_;T++){
int n,L,m;
cin>>n>>L>>m;
poly f(n);
for(int i=0;i<n;i++)cin>>f[i];
ranges::reverse(f);
poly g(L,1);
g=qpow(g,m,n);
f=mul(f,g);
f.resize(n);
cout<<"Case "<<T<<": ";
for(int i=n-1;i>=0;i--)cout<<f[i]<<" ";
cout<<"\n";
}
}2018沈阳M
多重背包问题,每个位置有数量为价值为的物品
多次在线询问,求选够价值个物品的方案
每个位置的生成函数为
答案为
void solve(){
int _;
cin>>_;
for(int T=1;T<=_;T++){
cout<<"Case #"<<T<<":\n";
int n,m;
cin>>n>>m;
const int M=2001;
vector<int> f(M),g(M);
vector sf(n+1,vector<int>(M)),sg(n+1,vector<int>(M));
f[0]=g[0]=sf[0][0]=sg[0][0]=1;
for(int i=1;i<M;i++)sf[0][i]=1;
auto _01=[&](vector<int> &dp,int x){
for(int i=M-1;i>=x;i--)dp[i]=(dp[i]-dp[i-x]+mod)%mod;
};
auto _complete=[&](vector<int> &dp,int x){
for(int i=x;i+x<M;i++)dp[i]=(dp[i]+dp[i-x])%mod;
};
for(int i=1;i<=n;i++){
int a,b;
cin>>a>>b;
_01(f,(a+1)*b);
_01(g,b);
_complete(f,b);
_complete(g,(a+1)*b);
sf[i][0]=f[0];
sg[i][0]=g[0];
for(int j=1;j<M;j++){
sf[i][j]=(sf[i][j-1]+f[j])%mod;
sg[i][j]=g[j];
}
}
int ans=0;
while(m--){
int l,r,c;
cin>>l>>r>>c;
l=(l+ans)%n+1,r=(r+ans)%n+1;
if(l>r)swap(l,r);
ans=0;
for(int i=0;i<=c;i++)ans=(ans+sf[r][i]*sg[l-1][c-i]%mod)%mod;
cout<<ans<<"\n";
}
}
}2020ICPC澳门

