反演小结
反演小结
有一类问题是这样的,已知\(f(n)=\sum_{某些条件}g(i)\),求\(g(n)\)
一般我们会根据这个式子求出一个新的式子:\(g(n)=\sum_{另一些条件}f(i)\)
我们将满足上面的形式的式子称为反演,反演是一类经典的问题,本文将对与反演有关的问题进行简单的介绍
二项式反演
简介
对于函数\(f(n)\)和\(g(n)\),如果满足
那么
证明的话考虑代入
即
注意到有\(\dbinom{n}{i} \dbinom{i}{j}=\frac{n!}{i!(n-i)!}*\frac{i!}{j!(i-j)!}=\frac{n!}{(n-i)!}*\frac{1}{j!(i-j)!}=\frac{n!}{j!(n-j)!}*\frac{(n-j)!}{(n-i)!*(i-j)!}=\dbinom{n}{j}\dbinom{n-j}{n-i}=\dbinom{n}{j}\dbinom{n-j}{i-j}\)
于是就有
类似于莫比乌斯反演(下面会提到),二项式反演也有它的另一种形式,有题目会用到
即已知
那么就有
证明的话同样考虑代入
二项式反演的本质其实就是容斥,关于其从容斥上的证明在此略去
我们来看几道题目
例题
错排问题
题目:给出一个正整数\(n\),求有多少个长为\(n\)的排列\(p\)满足\(p_i\neq i\)
设\(f_n\)为长为\(n\)的排列的个数,\(g_i\)表示长为\(n\)的排列中恰有\(i\)个错位的排列个数,那么就有
反演后可以得到
直接计算即可
染色问题
题目:有\(1-n\)共\(n\)个格子依次排在一排,用\(m\)种染料对它们染色,要求相邻两个格子的颜色不同且每种颜色均出现过至少一次,求方案数
设\(f_n\)为使用不超过\(n\)种颜色染色的方案数。\(g_n\)表示恰好使用\(n\)种颜色染色的方案数,答案就是\(g_m\)
反演之后就有
直接计算即可
莫比乌斯反演
注意一下莫比乌斯反演不只可以借助\(\mu\)函数进行求解,还可以直接通过计算因数的方法进行求解,这一般适用于需要求\(f_1-f_n\)的值的时候,比如说UOJ #62
z在这里附上vfk的题解(已经讲解的十分详细了):http://vfleaking.blog.uoj.ac/blog/62
以及自己的十分丑陋的代码
#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<vector>
#include<queue>
#include<map>
#include<set>
using namespace std;
typedef long long ll;
const int maxd=998244353,N=100000;
const double pi=acos(-1.0);
int n,c,d,q;
ll fr[100100],g[100100],b[100100],z[100100];
int read()
{
int x=0,f=1;char ch=getchar();
while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
return x*f;
}
ll qpow(ll x,ll y)
{
ll ans=1,sum=x;
while (y)
{
int tmp=y%2;y/=2;
if (tmp) ans=(ans*sum)%maxd;
sum=(sum*sum)%maxd;
}
return ans;
}
ll inv(ll x) {return qpow(x,maxd-2);}
void init()
{
n=read();c=read();d=read();q=read();
c%=(maxd-1);d%=(maxd-1);
c-=d;
c=(c+maxd-1)%(maxd-1);
int i,j;
for (i=1;i<=n;i++) fr[i]=qpow(i,c);
for (i=1;i<=n;i++)
{
for (j=i*2;j<=n;j+=i)
{
fr[j]=(fr[j]+maxd-fr[i])%maxd;
}
}
//for (i=1;i<=n;i++) cout << fr[i] << " ";cout << endl;
for (i=1;i<=n;i++)
{
fr[i]=inv(fr[i]);g[i]=inv(qpow(i,d));
}
//for (i=1;i<=n;i++) cout << fr[i] << " ";cout << endl;
}
void work()
{
int i,j;
for (i=1;i<=n;i++) {b[i]=read();b[i]=(b[i]*g[i])%maxd;}
for (i=1;i<=n;i++)
{
for (j=i+i;j<=n;j+=i)
{
b[j]=(b[j]+maxd-b[i])%maxd;
}
}
for (i=1;i<=n;i++)
{
if ((b[i]!=0) && (fr[i]==0)) {printf("-1\n");return;}
b[i]=(b[i]*fr[i])%maxd;
}
for (i=n;i>=1;i--)
{
for (j=i+i;j<=n;j+=i)
{
b[i]=(b[i]+maxd-b[j])%maxd;
}
}
for (i=1;i<=n;i++) b[i]=(b[i]*g[i])%maxd;
for (i=1;i<=n;i++) printf("%lld ",b[i]);printf("\n");
}
int main()
{
init();
while (q--) work();
return 0;
}
单位根反演
简介
单位根是什么?上网搜一篇\(FFT\)的博客,里面一定会提到单位根
或者您可以看这篇:https://www.cnblogs.com/zhou2003/p/10212036.html
注意到之后我们会用到单位根的诸多性质,理解单位根一定不要丢下单位圆
那么单位根反演又是什么呢?其实就是下面这个式子:
证明的话考虑分类讨论,当\(n|k\)时
但是当\(n\nmid k\)时,这就是一个简单的等比数列求和求和问题
利用这个东西我们可以求这样的一个东西:对于序列\(a_1,a_1,\cdots,a_n\),求\(\sum_{k|i}a_i\)
我们考虑序列\(a\)的生成函数\(f(x)\)
由单位根反演我们知道
将单位根带回原多项式求值就是\(FFT\)的前半部分(也就是\(DFT\)),可以在\(O(nlogn)\)的时间内完成
我们将上面这个问题推广一下,给出\(n,k,m\),求\(\sum_{i\text%k=m}a_i\)
比如说现在\(m=1\)
我们来看一下原生成函数的形式:\(f(x)=a_1x+a_2x^2+\cdots+a_nx^n\)
将其除以\(x\)之后得到\(f'(x)=a_1+a_2x+a_3x^2+\cdots+a_nx^{n-1}\)
也就是说我们将在\(f(x)\)代入的单位根在\(f'(x)\)中代入,就可以求出\(\sum_{i\text%k=1}\)的项了
这其实和将代入的单位根除以自己的\(m\)次幂是等价的
于是问题得到了圆满解决
(电脑没电了,明天补例题,就是咕咕了)