真的想点接触这个玩意。
cmd写的OI多项式💊是真的顶。
参考文献
人称OI药丸:https://www.luogu.com.cn/blog/command-block/wei-yun-suan-juan-ji-yu-ji-kuo-zhan
ZFY:你又从洛谷日报上抄袭,烦不烦呀。如果能有源码我还自己打!QAQ
例题
符号约定
ZFY:抄袭的第一步。。。
文中的多项式和下标均为向量,\([]\)表示向量拼接(但是\([\)命题\(]\)就是当命题成立时为\(1\))。
\(A_{i}\)表示\(A\)中的第\(i\)位,\(i_{j}\)表示\(i\)的二进制中从高往低的第\(j\)位。
ZWQ:你这和照搬有什么区别。。。为什么不能有自己的思考呢?像我一样。
\(⊕\)为位运算,&为和运算,^为异或,|为非运算。
规定\(n\)为总位数,且一定是\(2\)的次幂,记为\(2^u\)。
\(C[k]=\sum\limits_{i⊕j==k,0≤i,j≤n-1}A[i]*B[i]\),写成\((A*B=C)\),称为位运算\(⊕\)的卷积形式。
称\(DWT(A)\)为\(A\)线性代换后的向量。别看线性代换这个词很高端,这里的线性代换操作其实就是你有一行长度为\(k\)数组,和一个长度为\(k*k\)的矩阵,你用这个数组和矩阵第一行的每个数乘起来,相加(也就是\(a_1*b_1+a_2*b_2+...+a_k*b_k\)),得到一个数字,然后对每一行都做一遍,得到了一个新的数组,这个数组就是代换后的向量,然后此操作称为线性代换。(但是不是严格定于,这个是我个人理解,线性知识是大学知识呀,我哪知道。)
其实就是矩阵乘法,不过是一行乘一行而已。
矩阵\(c\)的第\(i\)行,第\(j\)列的数字就是\(c(i,j)\),称为变换系数。
通用思路
规律性
现在给你一个位运算的规则,我们一开始看毫无头绪,但是仔细想想也不会,FFT是怎么做的?
不就是通过线性代换后的点值直接乘吗,我们自然也希望这样子做。
但是首先就是确定矩阵是否具有某些性质,毕竟不是所有的矩阵都可以这么做!!!
也就是\(DWT(A)_{i}=\sum\limits_{j=0}^{n-1}c(i,j)A_{j}\)
然后满足当\(C=A*B\)时,\(DWT(C)_{i}=DWT(A)_{i}*DWT(B)_{i}\)。
也就是\(\sum\limits_{j=0}^{n-1}c(i,j)A_{j}*\sum\limits_{k=0}^{n-1}c(i,k)B_{k}=\sum\limits_{z=0}^{n-1}c(i,z)C_{j}\)
因为\(A*B=C\)
所以\(\sum\limits_{j=0}^{n-1}c(i,j)A_{j}*\sum\limits_{k=0}^{n-1}c(i,k)B_{k}=\sum\limits_{z=0}^{n-1}c(i,z)\sum\limits_{q⊕p==z}A_{q}B_{p}\)
\(\sum\limits_{j=0}^{n-1}\sum\limits_{k=0}^{n-1}c(i,k)c(i,j)A_{j}B_{k}=\sum\limits_{z=0}^{n-1}c(i,z)\sum\limits_{q⊕p==z}A_{q}B_{p}=\sum\limits_{q=0}^{n-1}\sum\limits_{p=0}^{n-1}A_{q}B_{p}c(i,q⊕p)\)
观察左右,发现了\(c(i,k)c(i,j)=c(i,j⊕k)\)。
限制性
首先,\(c\)矩阵的每一行不能是相似的,也不能某行某列全是\(0\)。(也就是不能是倍数关系或者相同,这种矩阵一般没有逆,似乎都是肯定的,因为可以证明,全是\(0\)的怎么乘都是\(0\),相似的话不可能一行的某个位置为\(0\),另外一行的相对位置不为\(0\))因为在后面求逆矩阵的时候严格要求此地方不能没有逆矩阵。
为了方便后面的操作,我们规定我们构造的矩阵满足一下性质:\(c(i,j)=c(i_0,j_0)c(i_1,j_1)...\)。这个我曾经问过cmd本人,他也没有持反对态度。貌似也没有持支持态度。。。
所以,我们可以用一个\(2*2\)的矩阵表示\(c\),我们称为\(cc\),其实\(cc\)就是\(c\)的第一行到第二行,第一列到第二列,也叫位矩阵。
但是,其实我们还是要证明一个东西,就是这个构造和上面的规律有没有冲突。
位运算的一个很大的特点,就是每一位只与这一位有关。
所以我们对于\(c(i,j)*c(i,k)=c(i,j⊕k)\)
设\(i⊕k=t\)
我们把前面的式子拆开就是:\((c(i_0,j_0)*c(i_1,j_1)*...*c(i_u,j_u))(c(i_0,j_0)*c(i_1,j_1)*...*c(i_u,j_u))*(c(i_0,j_0)*c(i_1,j_1)*...*c(i_u,j_u))=(c(i_0,j_0)*c(i_0,k_0))*(c(i_1,j_1)*c(i_1,k_1))*...*(c(i_u,j_u)*c(i_u,k_u))=c(i_0,t_0)*c(i_1,t_1)*...*c(i_u,t_u)=c(i,t)\)
所以互不影响。
可行性
这个可以用来证明为什么需要求逆矩阵,这是cmd所缺少的估计是他觉得太弱智了,也是为什么我要写这篇博客。
首先,这个\(c\)矩阵我们给他沿对角线翻转一下:
例如:\(\begin{Bmatrix}1 & 2 \\ 3 & 4\end{Bmatrix}\)翻折成了\(\begin{Bmatrix}1 & 3 \\ 2 & 4\end{Bmatrix}\)。
那么\(c\)矩阵翻折后变成了\(y\)矩阵。
那么现在的DWT真的就是原向量与\(y\)矩阵的矩阵乘法了。
那么我们求出了\(A*y\)(这里是矩阵乘法),但是怎么求出\(A\)呢?因为矩阵满足结合律,所以我们只要求出有\(y^{-1}\),即逆矩阵,然后再一乘,出来了。
现在我们又要证一个东西,\(cc^{-1}\)按照限制性构造可以构造出\(c^{-1}\)
这个看起来正确但是还是要证的。
假设命题成立。
首先,对于\(c\)与\(c^{-1}\),\(\sum\limits_{j=0}^{n-1}c_{i,j}*c^{-1}_{j,k}=[i=k]\)。
那么我们拆分一下:\(\sum\limits_{j=0}^{n-1}(cc(i_0,j_0)*cc(i_1,j_1)*...*cc(i_u,j_u))*(cc^{-1}(j_0,k_0)*cc^{-1}(j_1,k_1)*...*cc^{-1}(j_u,k_u))=[i=k]\)
然后我们针对第\(u\)位,也就是最后一位来看(即针对最后一位为\(0\)和为\(1\)的进行合并),乘法分配律一下就是:\(cc(i_0,j_0)*cc(i_1,j_1)*...*cc(i_{u-1},j_{u-1})*cc^{-1}(j_0,k_0)*cc^{-1}(j_1,0)*...*cc^{-1}(j_{u-1},k_{u-1})(cc(i_u,0)*cc^{-1}(0,k_u)+cc(i_u,1)*cc^{-1}(1,k_u))=cc(i_0,j_0)*cc(i_1,j_1)*...*cc(i_{u-1},j_{u-1})*cc^{-1}(j_0,k_0)*cc^{-1}(j_1,0)*...*cc^{-1}(j_{u-1},k_{u-1})*[j_0=k_0]\)
当\(j_0≠k_0\)时,式子直接为\(0\)。
否则我们就当作没有\(u\)这个位,然后对\(u-1\)位进行这个操作。
如果\(i=j\),那么二进制一定有一位不一样变成\(0\),否则到最后就是\(1\)吗,这么简单的事情。
所以,事实证明是可行的。
相信看了这么一大坨的你,会更容易理解cmd的博客的。
可分治性
既然刚刚我们构造了一些条件,我们就要用这些条件去为我们的题目服务。
像FFT那样,我们对于\(DWT\)左右拆半,然后化成了这样的东西:
\(DWT(A)_{i}=\sum\limits_{j=0}^{\frac{n}{2}-1}c(i,j)A_{j}+\sum\limits_{j=0}^{\frac{n}{2}-1}c(i,j+\frac{n}{2})A_{j+\frac{n}{2}}\)
然后我们去掉首位,就是(其中的\(i',j'\)是去掉首位的\(i,j\)):
\(c(i_{0},0)\sum\limits_{j=0}^{\frac{n}{2}-1}c(i',j')A_{j}+c(i_{0},1)\sum\limits_{j=0}^{\frac{n}{2}-1}c(i',j')A_{j}\)
猛然发现了新大陆,我们把:\(DWT(A)_{i}\)与\(DWT(A)_{i+\frac{n}{2}}\)进行对比。
\(DWT(A)_{i}=c(0,0)\sum\limits_{j=0}^{\frac{n}{2}-1}c(i',j')A_{j}+c(0,1)\sum\limits_{j=0}^{\frac{n}{2}-1}c(i',j')A_{j}\)
\(DWT(A)_{i+\frac{n}{2}}=c(1,0)\sum\limits_{j=0}^{\frac{n}{2}-1}c(i',j')A_{j}+c(1,1)\sum\limits_{j=0}^{\frac{n}{2}-1}c(i',j')A_{j}\)
那么我们就可以把过程分成\(log_{2}{n}\)层,用下一层合并到上一层,类似归并排序那样,会FFT的同学可以直接理解为FFT套板子,这个过程叫\(DWT\)。
但是\(IDWT\)呢?就是把向量数组转会异或数组,我们只需要再做一遍\(DWT\),只不过用的矩阵是\(cc\)的逆矩阵。
总体时间复杂度为:\(O(n\log{n})\)
完结撒花。
矩阵
这么快就完结撒花,你是不是在想桃子。
我们中间的矩阵都没说呢。
\(cc\)有以下要求:
- 满足:\(cc(i,j)*cc(i,k)=cc(i,j⊕k)\),这个常常用来检验\(cc(0,0)\)一般是\(1\)还是\(-1\)还是其他,当然检验其他格子也是可以的。
- 存在逆矩阵。
和运算
\(c(0,0)*c(0,0)=c(0,0)\),所以\(c(0,0)\)只能是\(0,1\)
同理可证所有的都只能是\(0,1\)。
所以检验之后只有以下矩阵:
\(\begin{Bmatrix} 0 & 1\\ 1 & 1\end{Bmatrix}\)和\(\begin{Bmatrix} 1 & 1\\ 0 & 1\end{Bmatrix}\)
秉承着一抄抄到底的态度我们采用第二种。
求逆即为:\(\begin{Bmatrix}1 &-1 \\ 0 & 1\end{Bmatrix}\)
非运算
和和运算一样,也全都是\(0,1\)
检验之后有以下矩阵:\(\begin{Bmatrix} 1&1 \\1 & 0\end{Bmatrix}\)和\(\begin{Bmatrix} 1& 0\\ 1&1 \end{Bmatrix}\)
-----cmd奆佬,Orz
我们依旧采用第二个矩阵,逆矩阵为:\(\begin{Bmatrix} 1&0 \\-1 & 1\end{Bmatrix}\)
异或运算
\(cc(0,0)*cc(0,y)=cc(0,0\)^\(y)=cc(0,y)\),所以\(cc(0,0)\)一定是\(1\)。
\(cc(0,1)*cc(0,1)=cc(0,1\)^\(1)=cc(0,0)=1\),所以\(cc(0,0)\)是\(1\)或\(-1\)。
\(cc(1,?)\)也是同理。
那么,经过检验有以下矩阵(这里cmd好像有点错误):\(\begin{Bmatrix} 1&1 \\1 & -1\end{Bmatrix}\)和\(\begin{Bmatrix} 1& -1\\ 1&1 \end{Bmatrix}\)
这里我们采用第一个矩阵。逆矩阵可以直接抄的事情他不香吗。
逆矩阵为:\(\begin{Bmatrix} 0.5& 0.5\\ 0.5 & -0.5\end{Bmatrix}\)
幸亏题目有\(\%\),不然就难受了。
代码
#include<cstdio> #include<cstring> #define N 210000 #define mod 998244353 #define inv2 499122177ll using namespace std; typedef long long LL; int n,m; LL cor[2][2]={{1,0},{1,1}},can[2][2]={{1,1},{0,1}},cxo[2][2]={{1,1},{1,mod-1}}; //矩阵 LL ior[2][2]={{1,0},{mod-1,1}},ian[2][2]={{1,mod-1},{0,1}},ixo[2][2]={{inv2,inv2},{inv2,mod-inv2}}; //逆矩阵 LL a[N],b[N],aa[N],bb[N],an1[N],an2[N],an3[N]; void fwt(LL *a,LL f[2][2]) { for(int i=2;i<=m;i<<=1) { int mid=(i>>1); for(int j=1;j<=m;j+=i) { int ed=j+mid-1; for(int k=j;k<=ed;k++) { LL x=a[k],y=a[k+mid]; a[k]=x*f[0][0]+y*f[0][1];a[k]%=mod; a[k+mid]=x*f[1][0]+y*f[1][1];a[mid+k]%=mod; } } } } void cpy(){for(int i=1;i<=m;i++)aa[i]=a[i],bb[i]=b[i];} int main() { scanf("%d",&n); m=1;for(int i=1;i<=n;i++)m<<=1; for(int i=1;i<=m;i++)scanf("%lld",&a[i]); for(int i=1;i<=m;i++)scanf("%lld",&b[i]); cpy(); fwt(aa,cor); fwt(bb,cor); for(int i=1;i<=m;i++)an1[i]=(aa[i]*bb[i])%mod; fwt(an1,ior); cpy(); fwt(aa,can); fwt(bb,can); for(int i=1;i<=m;i++)an2[i]=(aa[i]*bb[i])%mod; fwt(an2,ian); cpy(); fwt(aa,cxo); fwt(bb,cxo); for(int i=1;i<=m;i++)an3[i]=(aa[i]*bb[i])%mod; fwt(an3,ixo); for(int i=1;i<m;i++)printf("%lld ",an1[i]);printf("%lld\n",an1[m]); for(int i=1;i<m;i++)printf("%lld ",an2[i]);printf("%lld\n",an2[m]); for(int i=1;i<m;i++)printf("%lld ",an3[i]);printf("%lld\n",an3[m]); return 0; }
小结
规律性其实就是针对行的,为了能够分治,我们添加规则,使其满足性质,真是精致。
未完待续
- 秉承着抄到底的理念,下一次就抄完!!!
来源:https://www.cnblogs.com/zhangjianjunab/p/12185811.html