-
题目:有n个硬币,求出至少有k个硬币连续是正面的排列的种数。(1=<k<=n<=100)
-
分析:本题通过反面求解之后用总体相减比较方便。
至少有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]=2a[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] -
举例:输入6 2
需要求的是投掷6次硬币,连续正面的个数不超过1个的种类数。(代表满足条件的正反)
首先令a[0][1]=1, j=1
①1<2,无论正反都满足条件,所以a[1][1]=2a[0][1]=2
②2=2,此时不满足条件的值有【- +】这一种情况,所以a[2][1]=2a[1][1]-1=3
③3>2,此时不满足条件的有a[0][1]种,即【- - +】,所以a[3][1]=2a[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种 -
代码:
代码块一,如果测试用例在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;
}