反演小结
有一类问题是这样的,已知(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 eq 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
");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("
");
}
int main()
{
init();
while (q--) work();
return 0;
}
单位根反演
简介
单位根是什么?上网搜一篇(FFT)的博客,里面一定会提到单位根
或者您可以看这篇:https://www.cnblogs.com/zhou2003/p/10212036.html
注意到之后我们会用到单位根的诸多性质,理解单位根一定不要丢下单位圆
那么单位根反演又是什么呢?其实就是下面这个式子:
证明的话考虑分类讨论,当(n|k)时
但是当(n mid 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 ext%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 ext%k=1})的项了
这其实和将代入的单位根除以自己的(m)次幂是等价的
于是问题得到了圆满解决
(电脑没电了,明天补例题,就是咕咕了)