分治算法练习题 病毒分裂(重庆一中高2018级信息学竞赛测验6) 解题报告

【问题描述】  
  
  A学校的实验室新研制出了一种十分厉害的病毒。由于这种病毒太难以人工制造了,所以专家们在一开始只做出了一个这样的病毒。 


  这个病毒被植入了特殊的微型芯片,使其可以具有一些可编程的特殊性能。最重要的一个性能就是,专家们可以自行设定病毒的分裂能力 K,假如现在有x 个病毒,下一个分裂周期将会有 Kx个一模一样的病毒。你作为该实验室的数据分析员,需要统计出在分裂到第N个周期前,一共有多少个病毒单体进行了分裂。一开始时总是只有一个病毒,这个局面算作第一个周期。由于答案可能很大,专家们只需要你告诉他们对给定的P取模后的答案。
 
    
 【输入格式】  
  
  一行三个整数,依次是K, N, P。
 
    
 【输出格式】  
   
  一行一个整数,你的答案(对P取模) 。
 
    
 【输入样例】   
   
【样例1】
 5 3 7


【样例2】
 2 6 23
 
    
 【输出样例】  
   
【样例1】
 6


【样例2】
 8
 
    
 【样例解释】  
   
  样例一解释:第一个周期有 1 个病毒,产生了一次分裂。第二个周期有 1*5=5 个病毒, 这五个病毒都会分裂。 所以第三个周期前一共进行了1+5等于 6 次分裂。答案即为6 mod 7 = 6。
 
    
 【数据范围】  
   
1 < N < 10^18
1 < K , P < 2^31
 
    
 【来源】  
  

八中命题


做题思路(正解):根据题意,可推出第N个周期前病毒分裂次数为1(K^0)+K^1+K^2+...+K^(N-2),所以该题即可转化为求幂的和。要实现求幂的和,最简单的方法当然是直接暴力枚举,但很明显这样做会超时。本题的高效算法为分治算法,首先可以将答案中的1提出来,计算(K^1+K^2+...+K^(N-2))%P,这里就可以用到分治算法的二分,要算ans=(K^1+K^2+...+K^n)%P,可以先算出t=(K^1+K^2+...+K^n/2)%P,如果n为偶数,则ans=(t+t*(K^n/2))%P(即为(K^1+K^2+...+K^n/2+K^(n/2+1)+...+K^n)%P),如果n为奇数,则ans=(t+t*(K^n/2)+K^n)%P。在计算K的n次方时,同样可以用到二分,先算t=(K^n/2)%P,如果n为偶数,则K^n=(t*t)%P,如果n为奇数,则K^n=(t*t*K)%P。最后,答案即为求出的幂的和加1再对P取模。时间复杂度为O(log2N*log2N)。


#include
#include
#include
#include
#include
using namespace std;
typedef long long LL;
LL K,N,P;
LL qkpow(LL k,LL n)  //二分快速幂(递归写法)
{
	if(n==1)  return k%P;
	LL t=qkpow(k,n/2);
	LL ans=t*t%P;
	if(n%2==1)  ans=ans*k%P;
	return ans;
	/*LL ans=1,t=k;  //二分快速幂也可以使用二进制写法
	while(n>0)
	{
		if(n&1) ans=(ans*t)%P;
		t=t*t%P;
		n=n>>1;
	}
	return ans;*/
}
LL solve(LL k,LL n)  //二分计算第二周期开始的分裂次数(K^1+K^2+...+K^N-2)%P 
{
	if(n==1)  return k%P;
	LL t=solve(k,n/2);
	if(n%2==0)  
	{
		LL t1=qkpow(k,n/2);
		t=(t+t*t1%P)%P;
	}
	else
	{
		LL t1=qkpow(k,n/2),t2=qkpow(k,n);
		t=(t+t*t1%P+t2)%P;
	}
	return t;
}
int main()
{
	freopen("virus.in","r",stdin);
	freopen("virus.out","w",stdout);
	cin>>K>>N>>P;
	LL ans;
	if(N==2)  ans=1;
	else  ans=solve(K,N-2);
	cout<<(ans+1)%P<<'\n';  //第一周期的分裂次数为1 
	return 0;
}
考后反思:在考试时这道题没有拿到满分,最后发现原因是qkpow和solve函数里带的参数的类型是int,说明检查代码时还是不够仔细,这种错误下次可不能再犯了。

你可能感兴趣的:(竞赛测验,分治算法)