洛谷Coin Toss

  1. 题目:有n个硬币,求出至少有k个硬币连续是正面的排列的种数。(1=<k<=n<=100)

  2. 分析:本题通过反面求解之后用总体相减比较方便。
    至少有k个硬币连续是正面的反面:至多有k-1个连续是正面。
    用a[i][j]表示投掷硬币第i次连续正面不超过j个的种类数。
    ①当i<j+1时,新增加的硬币无论正反都满足条件,所以
    a[i][j]=2a[i - 1][j]
    ②当i=j+1时,只有前i-1个全部为正面,且第i个为正面才不满足不超过j个连续正面的条件,所以此时的种类数为减去该情况的个数
    a[i][j]=2
    a[i - 1][j]-1
    ③当i>j+1时,在i-1的基础上再抛一枚硬币,不满足条件的情况为:i-j到i-1这j个硬币均是正面,此时第i-j-1个硬币一定是反面,故共有a[i - j - 2][j]种可能是不满足条件的,所以
    a[i][j]=2*a[i - 1][j] - a[i - j - 2][j]

  3. 举例:输入6 2
    需要求的是投掷6次硬币,连续正面的个数不超过1个的种类数。(代表满足条件的正反)
    首先令a[0][1]=1, j=1
    ①1<2,无论正反都满足条件,所以a[1][1]=2
    a[0][1]=2
    ②2=2,此时不满足条件的值有【- +】这一种情况,所以a[2][1]=2a[1][1]-1=3
    ③3>2,此时不满足条件的有a[0][1]种,即【- - +】,所以a[3][1]=2
    a[2][1]-a[0][1]=5
    ④4>2,此时不满足条件的有a[1][1]=2种,即【* - +】,所以a[4][1]=a[3][1]-a[1][1]=8
    ⑤5>2,此时不满足条件的有a[2][1]=3种,即【* * - +】,所以a[5][1]=a[4][1]-a[2][1]=13
    ⑥5>2,此时不满足条件的有a[3][1]=5种,即【* * * - +】,所以a[6][1]=a[5][1]-a[3][1]=21
    所以最终的答案为2^6-21=43种

  4. 代码:
    代码块一,如果测试用例在10^19以内,能够用unsigned long long存储,直接通过递归计算即可。

#include<iostream>
#include<cstdio>
using namespace std;
unsigned long long a[101][101];
int main()
{
	int n, k;
	for (int i = 0; i <= 100; i++) {
		a[i][0] = 1;
		a[0][i] = 1;
	}
	for (int j = 1; j <= 100; j++)
		for (int i = 1; i <= 100; i++){
			if (i < j + 1) 
				a[i][j] = 2 * a[i - 1][j];
			else if (i == j + 1)
				a[i][j] = 2 * a[i - 1][j] - 1;
			else
				a[i][j] = 2 * a[i - 1][j] - a[i - j - 2][j];
		}
	
	while (scanf("%d%d", &n, &k) != EOF) {
		printf("%llu\n", a[n][n] - a[n][k - 1]);
	}
	system("pause");
	return 0;
}

代码块二,如果需要存储的数据超出了unsigned long long的表示范围,那么通过字符串保存数据,并通过字符串进行加减法运算。

#include<iostream>
#include<cstdio>
#include<stack>
#include<string>
#define max 100
using namespace std;
string a[max + 1][max + 1];//保存10000个可能的答案
inline string reverse(string x) {//通过堆栈将字符串反转
	stack<char> s;
	string result;
	int size = x.size();
	int i = 0;
	char c;
	while (i < size) {
		c = x[i];
		s.push(c);
		i++;
	}
	while (size--) {
		c = s.top();
		s.pop();
		result += c;
	}
	return result;
}
inline string add(string x)//计算2*x
{
	x = reverse(x);
	string result;
	int  carry = 0, n;
	unsigned i = 0;
	char cn;
	while (i < x.size()) {
		n = (x[i] - '0') * 2 + carry;
		if (n >= 10) {
			cn = n - 10 + '0';
			carry = 1;
		}
		else {
			cn = n + '0';
			carry = 0;
		}
		result += cn;
		i++;
	}
	if (carry)//加上最后的进位
		result += '1';
	return reverse(result);
}
inline string sub(string x, string y)//计算两个数相减,其中x>y
{
	string result;
	char n;
	int carry = 0;
	x = reverse(x);
	y = reverse(y);
	y.insert(y.end(),  x.size() - y.size(),'0');//将较短的y补0,使其与x一样长
	unsigned int i = 0;
	while (i < x.size()) {
		if (x[i] - carry >= y[i]) {
			n = x[i] - carry - y[i] + '0';
			carry = 0;
		}
		else {
			n = x[i] + 10 - carry - y[i] + '0';
			carry = 1;
		}
		result +=  n;
		i++;
	}
	result = reverse(result);
	int j = 0;
	while (result[j]=='0')//删除所有的前导0
		result = result.erase(j, 1);
	return result;
}
int main()
{
	int n, k;
	for (int i = 0; i <= max; i++)
		a[i][0] = a[0][i] = "1";
	for (int j = 1; j <= max; j++)
		for (int i = 1; i <= max; i++) {
			if (i < j + 1)//当i<j+1时,无论正反都满足条件,所以a[i][j]=2*a[i - 1][j]
				a[i][j] = add(a[i - 1][j]);
			else if (i == j + 1)//当i=j+1时,只有前i-1个全部为正面,且第i个为正面才不满足不超过j个连续正面的条件
				a[i][j] = sub(add(a[i - 1][j]), "1");//所以a[i][j]=2*a[i - 1][j]-1
			else//当i>j+1时,在i-1的基础上再抛一枚硬币,不满足条件的情况为:i-j到i-1这j个硬币均是正面,
				a[i][j] = sub(add(a[i - 1][j]), a[i - j - 2][j]);//此时第i-j-1个硬币一定是反面,共有a[i - j - 2][j]种可能
		}
	while (scanf("%d%d", &n, &k) != EOF) 
		cout << sub(a[n][n], a[n][k - 1]) << endl;
	system("pause");
	return 0;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值