ZOJ3690Choosing number

/*
题目:Choosing number
题目连接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3690
题目大意:


n个人站成一行,这儿有m个数字,1,2...m,每个人选择一个数字,但是如果相邻的两个人选择了同一个数字
这个数字不能超过k,请问他们有多少种选择数字的方式?


    解题思路:

我们一个人一个人的考虑,且从前往后,设两个函数G(i),F(i),G(i)表示前i-1个人选好后第i个人选择小于等于k数字时
的方案数,F(i)表示前i-1个人选好后第i个人选择大于k数字时的方案数,那我们得G(1)=k,F(1)=m-k;

 G(i)=F(i-1)*k+G(i-1)*(k-1); 
 当第i-1个人选大于k的数字时,第i个人可以选择1-k的任意数,所以加上F(i-1)*k,
 而当第i-1个人选小于等k的数字时,第i个人只要不和他选择的一样就可以了所以有(k-1)种情况;
 又因为选择1-k里面每个数字时等概率的,所以最后加上G(i-1)*(k-1);


 F(i)=(F(i-1)+G(i-1))*(m-k);
 当第i个人选大于k的数字时,第i-1个人可以任意选;


得到这个递推式后,我们可以构造一个转移矩阵得:


[F[i]]   [m-k,m-k  ]  [F[i-1]]
[G[i]]=[k,(k-1)/k] × [G[i-1]]


    然后用矩阵快速幂加速运算,最后的结果就是F(n)+G(n);

*/

#include<stdio.h>
#include<string.h>

#ifdef _WIN32
#define _LL __int64
#define _FIN "%I64d"
#else 
#define _LL long long
#define _FIN "%lld"
#endif
#define mod 1000000007

_LL m,k;

void init(_LL x[2][2])
{
	x[0][0]=x[0][1]=m-k;
	x[1][0]=k;x[1][1]=(k-1);
}

void mult(_LL x[2][2],_LL y[2][2])
{
	int i,j,k;
	_LL c[2][2];
	for(i=0;i<2;i++)
	{
		for(j=0;j<2;j++)
		{
			c[i][j]=0;
			for(k=0;k<2;k++)
				c[i][j]=(c[i][j]+(x[i][k]*y[k][j])%mod)%mod;
		}
	}
	for(i=0;i<2;i++) for(j=0;j<2;j++) x[i][j]=c[i][j];
}

_LL power(_LL y)
{
	_LL ans;
	_LL sum[2][2]={1,0,0,1};
	_LL x[2][2];init(x);
	while(y)
	{
		if(y&1)
			mult(sum,x);
		mult(x,x);
		y>>=1;
	}
	ans=(sum[0][0]*(m-k)%mod+sum[0][1]*k%mod)%mod;
	ans=(ans+(sum[1][0]*(m-k))%mod+(sum[1][1]*k)%mod)%mod;
	return ans;
}

int main()
{
	_LL n;
	while(scanf(_FIN,&n)!=EOF)
	{
		scanf(_FIN,&m);scanf(_FIN,&k);
		if(n==1) printf(_FIN,m);
		else printf(_FIN,power(n-1));
		printf("\n");
	}
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值