- (n=3^m)个人(标号为(0sim n-1))在玩剪刀石头布,共(t)轮,每轮(m)次。
- 把(x)的编号转化为一个三进制数,那么他在每轮的第(i)次都会一直出第(i)位数对应的剪刀((0))/石头((1))/布((2))。
- 给定一个评分数组(b_{u,v}),记第(i)轮结束后(x)的分数为(f_{i,x}),则第(i)轮过后(x)的分数会变成(sum_{y=0}^{n-1}f_{i-1,y} imes b_{W(x,y),L(x,y)}),其中(W(x,y))和(L(x,y))分别表示(x)对(y)赢和输的次数。
- 给定所有人初始的分数,求最终所有人的分数。
- (mle12,tle10^9),保证模数不为(3)的倍数
我已经尽力了,但仍旧无法完全理解。。。
三进制不越位加减法
总之我们先考虑一个暴力的矩乘做法,就相当于求出一个转移矩阵(B),那么答案就应该是:
但这样显然太暴力了,必须要进行优化。
这里我们先定义(xoplus y)和(xominus y)分别为三进制不进位加法和不退位减法。
然后我们分析一下剪刀石头布的定义,发现一个状态(u)胜于(v),当且仅当(uominus v=1),反之(u)输于(v)当且仅当(uominus v=2)。
这样就可以知道如果给对决双方的决策同时加上一个数或减去一个数,是不影响胜负的,所以就有(B_{i,j}=B_{ioplus k,joplus k}=B_{iominus k,jominus k})。
现在回到最开始的答案式,就可以发现其第(i)项应该满足:
于是这就被我们化成了一个卷积的形式,而且有用的实际上只有转移矩阵的第一行。
因此我们再去分析转移矩阵第一行的计算:
所以我们要做的相当于将(B)的第一行和自己做(t)次卷积,然后与(f_0)卷起来即可。
三进制(FWT)
现在我们需要做的是找一个三进制不进位加法的卷积变换,实际上它应该是一个循环卷积。
对于循环卷积的转移矩阵构造,这个矩阵据说叫作范德蒙德矩阵。
这个转移矩阵(T)的一般形式满足(T_{i,j}=omega_m^{ij},T^{-1}=omega_m^{-ij}),那么对于这里(m=3)的情况,我们能够得到:
而经过计算可以发现:
也就是说每次操作都会多乘一个(3),而我们总共会操作(log_3n)层,那么一共就乘上了(n),最后要除去。
(说句闲话,这样一来我们就可以理解一般(FFT)的一些令人迷惑的细节问题了,实际上它也是循环卷积,只不过我们卷积之前会把(n)扩大一倍,它就不会不会循环到之前的位置上去。而(IDFT)的时候要除以(n)的原因也是同理的。)
回到这道题,根据前面的推导发现我们需要扩域,把每个数表示成(a+bomega)的形式,其中(w)为三次单位根。
它的加减法是非常显然的,而乘法就考虑根据三次单位根的基本性质(1+omega+omega^2=0),我们可以把(omega^2)写成(-1-omega)。
所以就得到乘法公式:
这样一来这道题就做完了。
代码:(O(m3^m))
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 531441
#define M 12
using namespace std;
int n,m,t,X,b[M+5][M+5];struct node
{
int x,y;I node(CI a=0,CI b=0):x(a),y(b){}
I node operator + (Con node& o) Con {return node((x+o.x)%X,(y+o.y)%X);}
I node operator - (Con node& o) Con {return node((x-o.x+X)%X,(y-o.y+X)%X);}
I node operator * (Con node& o) Con {RI g=X-1LL*y*o.y%X;return node((1LL*x*o.x+g)%X,(1LL*x*o.y+1LL*y*o.x+g)%X);}//注意乘法
}f[N+5],g[N+5];
I node QP(node x,RI y) {node t=1;W(y) y&1&&(t=t*x,0),x=x*x,y>>=1;return t;}
I int exgcd(CI x,CI y,int& a,int& b) {return y?(exgcd(y,x%y,b,a),b-=x/y*a):(a=1,b=0);}
I void DFT(node* s)//正变换
{
RI i,j,k;node x,y,z;for(i=1;i^n;i*=3) for(j=0;j^n;j+=i*3) for(k=0;k^i;++k)
x=s[j+k],y=s[i+j+k],z=s[2*i+j+k],s[j+k]=x+y+z,s[i+j+k]=x+y*node(0,1)+z*node(X-1,X-1),
s[2*i+j+k]=x+y*node(X-1,X-1)+z*node(0,1);
}
I void IDFT(node* s)//逆变换
{
RI i,j,k;node x,y,z;for(i=1;i^n;i*=3) for(j=0;j^n;j+=i*3) for(k=0;k^i;++k)
x=s[j+k],y=s[i+j+k],z=s[2*i+j+k],s[j+k]=x+y+z,s[i+j+k]=x+y*node(X-1,X-1)+z*node(0,1),
s[2*i+j+k]=x+y*node(0,1)+z*node(X-1,X-1);
RI a,b;for(exgcd(n,X,a,b),a=(a%X+X)%X,i=0;i^n;++i) f[i].x=1LL*f[i].x*a%X;//注意除以n
}
int main()
{
RI i,j;for(scanf("%d%d%d",&m,&t,&X),n=i=1;i<=m;++i) n*=3;
for(i=0;i^n;++i) scanf("%d",&f[i].x);for(i=0;i<=m;++i) for(j=0;j<=m-i;++j) scanf("%d",&b[i][j]);
RI x,s1,s2;for(i=0;i^n;++i) {x=i,s1=s2=0;W(x) x%3==1&&++s1,x%3==2&&++s2,x/=3;g[i].x=b[s1][s2];}//预处理第一行
for(DFT(f),DFT(g),i=0;i^n;++i) f[i]=f[i]*QP(g[i],t);//循环卷积可以直接快速幂
for(IDFT(f),i=0;i^n;++i) printf("%d
",f[i].x);return 0;
}