zoukankan      html  css  js  c++  java
  • [清华集训2016]石家庄的工人阶级队伍比较坚强——三进制FWT

    题目链接:

    [清华集训2016]石家庄的工人阶级队伍比较坚强

    题目大意:有$n=3^m$个人玩石头剪刀布,共$t$轮游戏,每轮每个人要和包括自己的所有人各进行$m$次石头剪刀布。每个人在$m$轮中的决策固定,即为这个人编号的长度为$m$的三进制(其中$0$表示剪刀、$1$表示石头、$2$表示布,不足$m$位用$0$补齐)。每个人有一个初始分数$f_{0,x}$,给出一个分数矩阵$b$,其中$b_{i,j}$表示赢了$i$局输了$j$局的得分,在第$i$轮结束后,第$x$个人的分数为$f_{i,x}=sumlimits_{0le yle n}{ }f_{i-1,y}b_{u,v}$。其中$b_{u,v}$表示$x$与$y$石头剪刀布,$x$赢了$u$次,输了$v$次的得分。求$t$轮后每个人的得分。

    首先因为每个人的决策是固定的,所以转移关系也是恒定的,我们设它是一个矩阵$B$,其中$B_{i,j}$表示$i$与$j$游戏时$i$获得的分数。那么要求的就是$f_{n}=f_{0}B^n$。
    我们定义三进制不进位加法为$oplus$,不退位减法为$ominus$,他们互为逆运算(与二进制下的异或类似)。
    那么对于$forall k<3^m,B_{i,j}=B_{ioplus k,joplus k}$,同理$B_{i,j}=B_{iominus j,0}$。而这个结论也可以推广到$B^n$,即$forall k<3^m,B_{i,j}^{n}=b_{iominus j,0}^{n}$。
    那么$f_{n,i}=sumlimits_{k=0}^{3^m-1}f_{0,k}B_{k,i}^n=sumlimits_{k=0}^{3^m-1}f_{0,k}B_{0,iominus k}^n=sumlimits_{xoplus y=i}f_{0,x}B_{0,y}^n$
    所以,我们只需要$B$矩阵的第一行就够了,又因为
    $B_{0,i}^n=sumlimits_{k=0}^{3^m-1}B_{0,k}^{n-1}B_{k,i}=sumlimits_{k=0}^{3^m-1}B_{0,k}^{n-1}B_{0,iominus k}=sumlimits_{xoplus y=i}B_{0,x}^{n-1}B_{0,y}$
    所以只需要将$B_{0}$做$n$次卷积然后和$f_{0}$做一次卷积即可得到答案。
    也就是说我们需要找到一个三进制不进位加法卷积,这种模意义下的卷积也叫循环卷积。
    其实在我们熟悉的卷积中就有类似的存在——FWT的异或卷积
    可以发现FWT异或卷积实际上就是二进制不进位加法卷积。
    我们先来研究一下FWT异或卷积:
    设$bitcount(i)$表示$i$二进制中$1$的个数。
    因为$bitcount(i&k)$的奇偶性异或$bitcount(j&k)$的奇偶性等于$bitcount((ioplus j)&k)$的奇偶性
    那么我们就可以构造出正变换$F(k)=sum(-1)^{bitcount(i&k)}f(i)$
    可以发现上述式子中的$bitcount(i)$也就是$i$的二进制每一位数的和,而$&$就是二进制下的不进位乘法、$oplus$是二进制下不进位加法
    那么我们同样可以将这些扩展到三进制中,即$bitcount_{3}(i)$表示$i$的三进制每一位数的和、$&_{3}$表示三进制下的不进位乘法、$oplus_{3}$表示三进制下的不进位加法
    而上述结论在三进制下也依旧成立
    即$(bitcount_{3}(i&_{3}k)\%3)oplus_{3}(bitcount_{3}(i&_{3}k)\%3)=bitcount((ioplus_{3}j)&_{3}k)$
    那么现在只需要找到一个能代替上式中$-1$的数就能完成这个三进制不进位加法卷积了,而这个数$w$要满足$w^{i}=w^{i\%3}$,所以只要用三次单位根就行了($FWT$中的$-1$也就是二次单位根)!
    这里为了方便可以将所有复数都表示成$a+bw$,因为$w^2+w+1=0$,所以$w^2=-w-1$。
    对于两个复数相乘就是$(a+bw)*(c+dw)=ac+(bc+ad)w+bd(-w-1)=(ac-bd)+(ad+bc-bd)w$
    因为最后要乘上$3^m$的逆元,所以要求$3^m$与$p$互质。
    假设$p$有$3$这个质因子且$k=frac{p}{3}$,那么$frac{1}{k+1}+frac{1}{k(k+1)}=frac{1}{k}=frac{3}{p}$,与题面矛盾,因此保证了$3^m$有逆元。

    #include<set>
    #include<map>
    #include<queue>
    #include<stack>
    #include<cmath>
    #include<cstdio>
    #include<vector>
    #include<bitset>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define ll long long
    #define ull unsigned long long
    using namespace std;
    int n,m,t,mod;
    int a[20][20];
    int x[600010];
    int y[600010];
    struct miku
    {
    	int x,y;
    	miku(){}
    	miku(int X,int Y){x=X,y=Y;}
    	miku s(){return miku((mod-y)%mod,(x-y+mod)%mod);}
    	miku t(){return miku((y-x+mod)%mod,(mod-x)%mod);}
    	miku operator +(const miku &res){return miku((x+res.x)%mod,(y+res.y)%mod);}
    	miku operator -(const miku &res){return miku((x-res.x+mod)%mod,(y-res.y+mod)%mod);}
    	miku operator *(const miku &res){return miku((1ll*x*res.x%mod-1ll*y*res.y%mod+mod)%mod,(1ll*x*res.y%mod+1ll*y*res.x%mod-1ll*y*res.y%mod+mod)%mod);}
    }f[600010],g[600010];
    miku quick_pow(miku x,int y)
    {
    	miku res=miku(1,0);
    	while(y)
    	{
    		if(y&1)
    		{
    			res=res*x;
    		}
    		y>>=1;
    		x=x*x;
    	}
    	return res;
    }
    void exgcd(int a,int b,int &x,int &y)
    {
    	if(!b)
    	{
    		x=1,y=0;
    	}
    	else
    	{
    		exgcd(b,a%b,y,x);
    		y-=a/b*x;
    	}
    }
    int get_inv()
    {
    	int x,y;
    	exgcd(n,mod,x,y);
    	return x=(x%mod+mod)%mod;
    }
    void FWT(miku *a)
    {
    	for(int i=1;i<n;i*=3)
    	{
    		for(int l=i*3,j=0;j<n;j+=l)
    		{
    			for(int k=0;k<i;k++)
    			{
    				miku t[3]={a[j+k],a[j+k+i],a[j+k+i+i]};
    				a[j+k]=t[0]+t[1]+t[2];
    				a[j+k+i]=t[0]+t[1].s()+t[2].t();
    				a[j+k+i+i]=t[0]+t[1].t()+t[2].s();
    			}
    		}
    	}
    }
    void IFWT(miku *a)
    {
    	for(int i=1;i<n;i*=3)
    	{
    		for(int l=i*3,j=0;j<n;j+=l)
    		{
    			for(int k=0;k<i;k++)
    			{
    				miku t[3]={a[j+k],a[j+k+i],a[j+k+i+i]};
    				a[j+k]=t[0]+t[1]+t[2];
    				a[j+k+i]=t[0]+t[1].t()+t[2].s();
    				a[j+k+i+i]=t[0]+t[1].s()+t[2].t();
    			}
    		}
    	}
    	int inv=get_inv();
    	for(int i=0;i<n;i++)
    	{
    		a[i].x=1ll*a[i].x*inv%mod;
    	}
    }
    int main()
    {
    	scanf("%d%d%d",&m,&t,&mod);
    	n=1;
    	for(int i=1;i<=m;i++)
    	{
    		n*=3;
    	}
    	for(int i=0;i<n;i++)
    	{
    		scanf("%d",&f[i].x);
    		x[i]=x[i/3]+(i%3==1);
    		y[i]=y[i/3]+(i%3==2);
    	}
    	for(int i=0;i<=m;i++)
    	{
    		for(int j=0;i+j<=m;j++)
    		{
    			scanf("%d",&a[i][j]);
    		}
    	}
    	for(int i=0;i<n;i++)
    	{
    		g[i].x=a[x[i]][y[i]];
    	}
    	FWT(f);
    	FWT(g);
    	for(int i=0;i<n;i++)
    	{
    		f[i]=f[i]*quick_pow(g[i],t);
    	}
    	IFWT(f);
    	for(int i=0;i<n;i++)
    	{
    		printf("%d
    ",f[i].x);
    	}
    }
  • 相关阅读:
    Java并发编程:Lock
    java多线程中的生产者与消费者之等待唤醒机制@Version2.0
    java多线程中的生产者与消费者之等待唤醒机制@Version1.0
    vim 自动补全
    git 忽略文件的三种方式
    vim 查找整个工程
    vim 寄存器的使用
    我的vim插件列表
    web前端面试系列 一 js闭包
    web前端面试系列
  • 原文地址:https://www.cnblogs.com/Khada-Jhin/p/10272505.html
Copyright © 2011-2022 走看看