HDU2065 "红色病毒"问题(数学)

"红色病毒"问题

Problem Description
 医学界发现的新病毒因其蔓延速度和Internet上传播的"红色病毒"不相上下,被称为"红色病毒",经研究发现,该病毒及其变种的DNA的一条单链中,胞嘧啶,腺嘧啶均是成对出现的。
现在有一长度为N的字符串,满足一下条件:
(1) 字符串仅由A,B,C,D四个字母组成;
(2) A出现偶数次(也可以不出现);
(3) C出现偶数次(也可以不出现);
计算满足条件的字符串个数.
当N=2时,所有满足条件的字符串有如下6个:BB,BD,DB,DD,AA,CC.
由于这个数据肯能非常庞大,你只要给出最后两位数字即可.

Input
 每组输入的第一行是一个整数T,表示测试实例的个数,下面是T行数据,每行一个整数N(1<=N<2^64),当T=0时结束.

Output
 对于每个测试实例,输出字符串个数的最后两位,每组输出后跟一个空行.

Sample Input
4
1
4
20
11
3
14
24
6
0

Sample Output
Case 1: 2
Case 2: 72
Case 3: 32
Case 4: 0

Case 1: 56
Case 2: 72
Case 3: 56

分析

看他给的数据每个数据最多可以到264,而且还有多组数据,说明log(n)的算法肯定是不行了,也就是说不能从1扫描到n,所以考虑直接通过n求出最后的答案。
同样的,我们尝试找一下规律,
当n = 1时,有两种情况,B 和 D
当n = 2时,有六种情况,BB , BD , DB , DD , AA 和 CC
实际上这里发现B和D应该是同一种东西(但AA和CC不应该视作同一种东西,它能够出现BDBD的情况,也能出现ACAC的情况),
有n个位置能放的话,那么就没有A和C的情况下,就有2n个方案,因为A和C的总和必须是偶数,然后我想到的是从n中拿出偶数个放上A和C,剩下的放上B和D。我尝试写出写出了以下式子,
当n = 4时,ans = 24 + C42 * 2 * 22 + C44 * 23 * 20
在n = 4 的情况下,我就把情况分成了三种,

  1. 没有A和C的情况,只有B和D,那么就应该是24
  2. 有两个A和C,那么只能是AA或CC两种,剩下的两个也有22种排列,所以那就是C42 * 2 * 22种情况
  3. 都是A和C,那么就可以组合出AACC , ACAC , CACA, CCAA,AAAA , CCCC , ACCA , CAAC八种情况,实际上就是2n-1种情况(这里分析见文末)

再把上述几种情况加起来就得到了 ans = 72 的结论,发现和题目给的是一样的,然后我又算了一个当n = 6的情况,同样和题目给的条件符合。抽象出公式,
ans = 2n + Cn2 * 21 * 2n-2 + Cn4 * 23 * 2n-4 + … + Cnn * 2n-1 * 2n-n
然后发现算式能够化简成为,

ans = 2n + 2n-1 * (Cn2 + Cn4 + … + Cnn-2 + Cnn)

后面的组合数还能够再次化简,

ans = 2n + 2n-1 * (2n-1 - 1)

所以,
ans = 2n + 22n-2 - 2n-1

ans = 4n-1 + 2n-1

然后题目要求出ans的最后两位,我们知道ab末尾两位是循环的,这里列出循环节,2n = [1,2,4,8,16,32,64,28,56,12,24,48,96,92,84,68,36,72,44,88,76,52,4,8,16,32,64,28,56,12],后面都是循环前面的内容,因此2n-1末尾两个数字可以在O(1)的时间求得。同理,4n = [1,4,16,64,56,24,96,84,36,44,76,4,16,64,56,24],4n的数值也可以在O(1)求得。
所以在这里,能够直接通过题目给的数字n求得答案,时空复杂度都是O(1)。我当时怎么就没想到快速幂,快速幂似乎也挺方便的喂

完整代码如下,

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a[66],b[30];
int calcN(ll n) {
	if (n == 1) return 2;
	if (n == 2) return 6;
	int t1 = (n - 1) % 10;
	int t2 = (n - 1) % 20;
	return (b[t1] + a[t2]) % 100;;
}
int main() {
	for (int i = 20; i < 40; i++) {
		a[i - 20] = ((ll)1 << i) % 100;
	}
	for (int i = 10; i < 20; i++) {
		b[i - 10] = ((ll)1 << (2 *i)) % 100;
	}
	int e;
	while (~scanf("%d", &e),e) {
		for(int i = 1 ; i <= e ;++i){
			ll n;
			scanf("%lld", &n);
			printf("Case %d: %d\n", i, calcN(n));
		}
		puts("");
	}
	return 0;
}

先打表直接出结果,这里还遇到一个大坑,循环节交接的地方出现了一个很大的问题,调试了一段时间才发现,221末尾两位是52 而不是2,所以干脆直接加了个特判,并且设置了循环节移位计算,之后每一次直接调用函数出结果。

当时也想到了两个循环节函数相加,那么所得的答案也应该有循环节,一个循环节20一个循环节10,所以实际上最后的答案也有个循环节是20…所以只要从1写到30就能发现这个规律了吧那可能要算死

然后再来想想对于n位长度,都是A和C并且A和C必须是偶数(可以为零),他应该有多少种可能性,
用n = 4举例,
首先在4中选出0个A,那么剩下都是C,这是一种情况
然后在4中选出2个A,那么还有2个C,这就有C42种情况
最后在4中选出4个A,那么没有C,这又是一种情况
推广到n,算式应该如下
idx = Cn0 + Cn2 + Cn4 + … + Cnn = 2n-1

这个idx就是最上面式子中
ans = 2n + Cn2 * 21 * 2n-2 + Cn4 * 23 * 2n-4 + … + Cnn * 2n-1 * 2n-n

至此这道题就算解完了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值