QQ 的排列

QQ 的排列

Description

QQ向来对排列问题很感兴趣。什么全排列呀,置换群呀,都是QQ曾经研究过的问题。现在,一般难度的排列问题已经难不倒QQ了,QQ想要更高程度的挑战。于是QQ经常找全全和样样切磋很多高难度的题目。全全和样样都是研究排列的百年难得一遇的奇才,对此也是乐此不疲哦。

最近,他们发现他们三个人的昵称居然都是由重复的字组成的。这个可是一个很有趣的现象呀。于是,QQ的奇思妙想又出来了,他们每个人的昵称的字都提取出来,这样就获得了Q,全,样这三个字,他希望用这三个字重新组成新的昵称,每个字都可以用无限次,当然这样的昵称很可能很难听。 QQ又在这些排列上加了一个附加的条件,就是Q这个字母不能连续出现两次。

比如说,我们令Hn表示当昵称由n个字组成时,问有多少个符合条件的昵称。

例如:

H1=3,具体方案:Q,样,全

H2=8,具体方案:Q样,Q全,样Q,样全,样样,全Q,全样,全全

再令Sn=H1+H2+…+Hn-1+Hn;

QQ的问题出来了,他想知道Sn,但是这个Sn有可能太大了,所以QQ只想知道这个Sn9937取余的结果。

Input

先输入一个数T,表示有多少组测试数据

随后的T行,每行一个数n

n的范围是11000000000

Output

对于每个数n,计算Sn mod 9937

Sample Input

2
1
2

Sample Output

3
11

HINT

Source

浙师大2010校赛

这道题是典型的组合数学题,假设长度为n(正好长度为n,不包括长度小于n的)的方法g(n),sum(n)为长度不大于n的总和;

sum(n) = g(1)+ g(2)+ …… + g(n);

那么我们可以分析如下 g(n) = 2*g(n-1)+2*g(n-2);

其中2*g(n-1)是第一个字符为'全'或者'样'的时候的种类,当第一个字符为'Q'的时候,那么第二个字符只能是'全'或者'样',那么这样第一个字符和第二个字符就不会影响到后面的字符,所以第一个字符为'Q'的时候,种类为2*g(n-2)。我们可以得到如下一些式子:

g(3) = 2*g(2) + 2*g(1);

g(4) = 2*g(3) + 2*g(2);

……

g(n)= 2*g(n-1) + 2*g(n-2);

那么将这n-2个式子加起来可以得到sum(n) = 2*sum(n-1)+2*sum(n-2)+g(2)- g(1);

易知g(1) = 3,g(2) = 8;

可以得到如下一个矩阵

0 1 0sum(n-2)

2 2 5 = A  sum(n-1) = B

0 0 11

其中A矩阵中的5是等于g(2)- g(1),A矩阵乘以B矩阵可以得到的矩阵C=(sum(n-1), sum(n), 1)

所以我们现在要做的事对于(sum(1), sum(2), 1)做n-2次A矩阵的左乘,所以利用二分求幂的思想,利用log(n)的复杂度既可以将问题求解出来。

#include <cstdio>
 
 void MatrixMul(int fri[][3], int next[][3])
 {
 	int cnt[3][3];
 	for ( int i = 0; i < 3; ++i )
 	{
 		for ( int j = 0; j < 3; ++j )
 		{
 			cnt[i][j] = 0;
 			for ( int k = 0; k < 3; ++k )
 			{
 				cnt[i][j] += fri[i][k] * next[k][j];
 			}
 			cnt[i][j] = cnt[i][j] % 9937;
 		}
 	}
 	for ( int i = 0; i < 3; ++i )
 	{
 		for ( int j = 0; j < 3; ++j )
 		{
 			next[i][j] = cnt[i][j];
 		}
 	}
 }
 
 int getAns(int n)
 {
 	if ( n == 1 )
 	{
 		return 3;
 	}
 	if ( n == 2 )
 	{
 		return 11;
 	}
 
 	int key[3][3] = {{0,1,0},{2,2,5},{0,0,1}}, ans[3][3] = {{0,1,0},{2,2,5},{0,0,1}};
 	int temp[3][1] = {3, 11, 1};
 
 	n -= 3;
 	while ( n )
 	{
 		if ( n&1 )
 		{
 			MatrixMul(key, ans);
 		}
 		n = n >> 1;
 		MatrixMul(key, key);
 	}
 	int num = 0;
 	for ( int i = 0; i < 3; ++i )
 	{
 		num += ans[1][i] * temp[i][0];
 	}
 
 	return num % 9937;
 }
 
 int main(int argc, char ** argv)
 {
 	int t, n;
 	scanf("%d", &t);
 	while ( t-- )
 	{
 		scanf("%d", &n);
 		printf("%d\n", getAns(n));
 	}
 	return 0;
 }
 


  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值