JZOJ 6481. 【GDOI2020模拟02.22】黎曼几何(矩阵乘法)

JZOJ 6481. 【GDOI2020模拟02.22】黎曼几何

题解

  • f n , 1 f_{n,1} fn,1 f n , 2 f_{n,2} fn,2分别表示将 n n n个硬币移动 1 1 1格和 2 2 2格的最小步数,
  • 推一推可以得到转移:
  • f n , 1 = f n − 1 , 2 × 2 + 1 f_{n,1}=f_{n-1,2}\times 2+1 fn,1=fn1,2×2+1
  • f n , 2 − f n − 1 , 1 + f n − 1 , 2 × 2 + 2 f_{n,2}-f_{n-1,1}+f_{n-1,2}\times 2+2 fn,2fn1,1+fn1,2×2+2
  • 具体解释:
  • 移动 1 1 1格,先将 n − 1 n-1 n1个移动 2 2 2格,再把最下面的移动 1 1 1格,最后把 n − 1 n-1 n1个移动 2 2 2格到达原来的第二格;
  • 移动 2 2 2格,先将 n − 1 n-1 n1个移动 2 2 2格,再把最下面的移动 1 1 1格,接着 n − 1 n-1 n1个移动 1 1 1格,最下面的移动 1 1 1格,最后把 n − 1 n-1 n1个移动两格达到原来的第三格。
  • 但是这样是 O ( n ) O(n) O(n)的,优化很显然是用矩阵乘法,
  • 具体实现需要减小常数。

代码

#include
#include
using namespace std;
#define ll long long
#define md 998244353
ll n;
ll f[3][100010][3][3],p[3][3],q[3][3];
ll read()
{
	ll s=0;
	char x=getchar();
	while(x<'0'||x>'9') x=getchar();
	while(x>='0'&&x<='9') s=s*10+x-48,x=getchar();
	return s;
}
int main()
{
	int tn,i,j,k,l,h;
	scanf("%d",&tn);
	memset(f,0,sizeof(f));
	f[0][1][0][0]=1;
	f[0][1][0][1]=1;
	f[0][1][0][2]=2;
	f[0][1][1][2]=1;
	f[0][1][2][1]=2;
	f[0][1][2][2]=2;
	for(i=0;i<3;i++)
	{
		if(i)
		{
			for(j=0;j<3;j++)
				for(k=0;k<3;k++)
					for(l=0;l<3;l++) f[i][1][j][k]=(f[i][1][j][k]+f[i-1][99999][j][l]*f[i-1][1][l][k]%md)%md;
		}
		for(j=2;j<=99999;j++)
		{
			for(k=0;k<3;k++)
				for(l=0;l<3;l++) 
					for(h=0;h<3;h++) f[i][j][k][l]=(f[i][j][k][l]+f[i][j-1][k][h]*f[i][1][h][l]%md)%md;
		}
	}
		
	ll ans1=0,ans2=0;
	while(tn--)
	{
		n=read();
		n--;
		for(i=0;i<3;i++)
			for(j=0;j<3;j++) p[i][j]=i==j;
		int i=0;
		while(n)
		{
			int x=n%100000;
			n/=100000;
			if(x)
			{
				for(j=0;j<3;j++)
					for(k=0;k<3;k++)
					{
						q[j][k]=0;
						for(l=0;l<3;l++) q[j][k]=(q[j][k]+p[j][l]*f[i][x][l][k]%md)%md;
					}
				for(j=0;j<3;j++)
					for(k=0;k<3;k++) p[j][k]=q[j][k];
			}
			i++;
		}
		ll s1=(p[0][1]+p[1][1]+p[2][1]*2)%md;
		ll s2=(p[0][2]+p[1][2]+p[2][2]*2)%md;
		ans1^=s1,ans2^=s2;
	}
	printf("%lld %lld",ans1,ans2);
	return 0;
}

你可能感兴趣的:(题解)