P11739 朴素推导
NaCly_Fish · · 题解
零
尝试用尽可能少的前置知识来推导。
如果你对这种大力推导的方法比较感兴趣,那我推荐你来看看。
看到这种问题,一般的想法是对于
可是这里没那么简单,比如对于子问题 1,我们可以用
其中
因为这个对
一
不过原问题还是太复杂,可以先考虑这样一个特别简化后的情况:不必满足
此时要求的就是「将一个正整数拆分为若干互异自然数之和」的方案数,我们容易写出答案的生成函数为:
等号左边就是我们刚才写过的,但右边有什么用,又是怎么得到的呢?
这个东西厉害的地方在于它可以很容易扩展至多元的情况,即:
这样就可以轻松解决不限制
非常的简单方便。
对于刚才的第二个问题,答案也很简单:求导即可。
设
那么
再对等式两边同求
边界条件自然是
对于子问题 2 当然也很简单,把
二
如果要对子问题 1 引入不为
按上面的方法算一遍,同时我们简记
那么就有
再设
很显然
比如
那这显然就和分拆数问题对应起来了,
有同学看到这里可能按捺不住了:「Burnside 引理不就直接秒了吗?」但是 Burnside 引理还是太高级了,我们要用更朴素的方法。
三
我们把
设
只关注其中一项,比如
这里就是枚举了三种
扩展到一般的情况,这一项的系数就是对于全部
再仔细想想,可以将其对应到「确定
所以这个式子实际算的就是:
现在有了每一项的系数,就可以直接展开
四
将所有
其中
将分子展开,最终就只需要多次给出
设
那么只需要计算出
五
参考代码如下,加了一些注释,可以补充文中没描述清楚的地方:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 203
#define p 1000000021
#define ll long long
using namespace std;
inline int power(int a,int t){
int res = 1;
while(t){
if(t&1) res = (ll)res*a%p;
a = (ll)a*a%p;
t >>= 1;
}
return res;
}
int fac[N],ifac[N],inv[N];
void init(int n){
fac[0] = ifac[0] = inv[1] = 1;
for(int i=1;i<=n;++i) fac[i] = (ll)fac[i-1]*i%p;
ifac[n] = power(fac[n],p-2);
for(int i=n-1;i;--i) ifac[i] = (ll)ifac[i+1]*(i+1)%p;
for(int i=2;i<=n;++i) inv[i] = (ll)fac[i-1]*ifac[i]%p;
}
inline int binom(int n,int m){
return (ll)fac[n]*ifac[m]%p*ifac[n-m]%p;
}
int gcd(int a,int b){ return b==0?a:gcd(b,a%b); }
int lcm(int a,int b){ return a*b/gcd(a,b); }
int interpolate(const int *f,int n,int x){ // give n-degree poly f values f(0) , ... , f(n), return f(x).
static int pre[N],suf[N];
n += 1,x += 1;
pre[0] = suf[n+1] = 1;
for(int i=1;i<=n;++i) pre[i] = (ll)pre[i-1]*(x-i)%p;
for(int i=n;i;--i) suf[i] = (ll)suf[i+1]*(x-i)%p;
int ans = 0,tmp;
for(int i=1;i<=n;++i){
tmp = (ll)f[i-1]*ifac[i-1]%p*ifac[n-i]%p;
if((n-i)&1) tmp = -tmp;
ans = (ans + (ll)tmp*pre[i-1]%p*suf[i+1])%p;
}
return (ans+p)%p;
}
ll a[N],b[N];
int c[N],tc[N],val[53][N];
int n,k,ans1,ans2;
int delt;
void dfs(int dep,int cf){ // expand \prod_{i=1}^k (U_i - V_i)^{c_i}
if(dep > k){
int flag = 0;
for(int i=1;i<=k;++i){
cf = (ll)cf*binom(c[i],tc[i])%p;
flag += tc[i];
}
if(flag&1) cf = -cf;
int f[101]; // the numerator. f(z) = \prod_{i=1}^k (1-z^i)^{tc_i}
memset(f,0,sizeof(f));
f[0] = 1;
int deg = 0;
for(int i=1;i<=k;++i)
for(int t=1;t<=tc[i];++t){
for(int j=deg+i;j>=i;--j)
f[j] = (f[j] - f[j-i])%p;
deg += i;
}
int tmp = 1;
for(int i=1;i<=n;++i){
int sum = 0;
for(int j=0;j<=deg;++j)
sum = (sum + (ll)f[j]*val[i][j])%p;
tmp = (ll)tmp*sum%p;
}
flag = 0;
for(int i=1;i<=k;++i) flag += (i+1)*c[i];
ans2 = (ans2 + (ll)tmp*cf)%p;
if(flag&1) tmp = p-tmp;
ans1 = (ans1 + (ll)tmp*cf)%p;
return;
}
for(int i=0;i<=c[dep];++i){
tc[dep] = i;
dfs(dep+1,cf);
}
}
void solve(){
int d = 1,cf = 1,m = 0;
for(int i=1;i<=k;++i){
if(c[i] > 0) d = lcm(d,i);
m += c[i];
cf = (ll)cf*ifac[c[i]]%p*power(inv[i],c[i])%p;
}
static int f[10003],g[2000][N];
memset(f,0,sizeof(f));
memset(val,0,sizeof(val));
int L = d*(m+1);
f[0] = 1;
for(int i=1;i<=k;++i)
for(int t=1;t<=c[i];++t)
for(int j=i;j<=L;++j)
f[j] = (f[j] + f[j-i])%p;
for(int i=0;i<d;++i)
for(int j=0;j<=m;++j)
g[i][j] = f[j*d+i];
for(int i=1;i<=n;++i)
for(int j=0;j<=k;++j){
if(a[i] < j*(b[i]+1)) break;
ll x = a[i] - j*(b[i]+1);
val[i][j] = interpolate(g[x%d],m,(x/d)%p);
// val[i][j] = [z^{a_i - j(b_i+1)}] \prod_{t=1}^k 1/(1-z^t)^{c_t}
}
dfs(1,cf);
}
void partition(int sum,int dep){ // find all partition of k
if(sum == k){
solve();
return;
}
if(dep > k-sum) return;
for(int i=0;sum+i*dep<=k;++i){
c[dep] = i;
partition(sum+i*dep,dep+1);
}
c[dep] = 0;
}
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
for(int i=1;i<=n;++i) scanf("%lld",&b[i]);
init(103);
partition(0,1);
printf("%d %d",(ans1+p)%p,(ans2+p)%p);
return 0;
}