uva 10328 - Coin Toss 投硬币(dp递推,大数)
题意:抛出n次硬币(有顺序),求至少k个以上的连续正面的情况的种数。
思路:转换成求抛n个硬币,至多k-1个连续的情况种数,用所有可能出现的情况种数减去至多k-1个的情况,就得到答案了。此题涉及大数加减。
分析:
(1)假设n=k,那么只有一种情况。
(2)假设n=k+1,那么有3种情况,包含k个的两种,k+1个的一种。
(3)假设k=1,那么只有无正面这一种的情况不能被考虑,其他都能算,那么就是(1<<n)-1种。(n个硬币有2^n种结果)
(4)其他情况考虑递推。先把问题的规模降低,最小就是1了,再逐渐增大,每次规模增加1,直到n为止。用dp[n+1]这么大的数组就够了,而dp[n]表示从第1个硬币到第n个硬币中最多出现k-1个连续正面的情况种数。到这步,可以直接将拿到的k自减一,假设结果为t。那么问题转成求抛第i个硬币时,它和它前面不能超过t个正面。
(5)基于上面第4步的分析。可以看出,在抛第t个之前(包括第t个),最多就出现t个正面,不可能超过抛的次数的。那么dp[0~k]可以一次性初始化为1<<i,也就是有这么多抛法其结果都不会超过t个正面啦。
(6)考虑在抛第k+1个的情况,顶多也就一种不合法,也就是全面都是正面,那么种数就是(1<<k+1)-1。
(7)考虑在抛大于第k+2的情况,不妨设为dp[k+2]=dp[k+1]*2,但是这里面包含了大于t个的情况,只可能出现这样的情况:第k+2个刚好抛到正面,如果其前面与其连续的刚好有t个正面,那么就会造成连续t+1个正面,非法!但是不可能出现多于t+2个连续的可能,如果多于t+2,那么假设当前为正面,其前面必须有t+1个连续,而dp[k+1]中不会记录有非法的情况,所以不考虑。那么出现与第k+2个刚好连起来是t+1个连续的情况会有多少种?(k+2)-t-1这个位置肯定是反面的,不然就会造成连续t+2个了,那么在k+2-t-2及其之前所有不超过t个连续的情况种数就是出现“与k相连t+1个正面”的情况种数,减去这个数量即可。
总之,对于i大于k+2的,dp[i]=dp[i-1]*2-dp[i-t-2]。推到dp[n]时,所有可能都出来了。用所有可能数减去此数:答案= (1<<n)-dp[n]。
1 #include <iostream> 2 #include <vector> 3 #include <cstdio> 4 #include <cstring> 5 #include <string> 6 #include <algorithm> 7 using namespace std; 8 int n, k; 9 long long a[70]; 10 vector<string> ans; 11 vector<string> pre; 12 13 bool comp(string num1,string num2) 14 { 15 int leng=num1.length(),i; 16 for(i=0;i<leng;i++){if(num1[i]!='0')break;} 17 num1=num1.substr(i,leng); 18 if(num1.length()==0)num1="0"; 19 20 leng=num2.length(); 21 for(i=0;i<leng;i++){if(num2[i]!='0')break;} 22 num2=num2.substr(i,leng); 23 if(num2.length()==0)num2="0"; 24 25 if(num1.length()>num2.length())return true; 26 else if(num1.length()==num2.length()) 27 { 28 if(num1>=num2)return true; 29 else return false; 30 } 31 else return false; 32 } 33 string _minus(string num1,string num2) 34 { 35 string result; 36 if(comp(num2,num1)){string ss=num1;num1=num2;num2=ss;} 37 reverse(num1.begin(),num1.end()); 38 reverse(num2.begin(),num2.end()); 39 40 result=""; 41 42 int i; 43 for(i=0;i<int(num1.length())&&i<int(num2.length());i++) 44 { 45 char c=num1[i]-num2[i]+48; 46 result=result+c; 47 } 48 if(i<int(num1.length())) 49 for(;i<int(num1.length());i++) 50 result=result+num1[i]; 51 52 int jiewei=0; 53 for(i=0;i<int(result.length());i++) 54 { 55 int zhi=result[i]-48+jiewei; 56 if(zhi<0) {zhi=zhi+10;jiewei=-1;} 57 else jiewei=0; 58 result[i]=(char)(zhi+48); 59 } 60 61 for(i=result.length()-1;i>=0;i--) 62 if(result[i]!='0') 63 break; 64 65 result=result.substr(0,i+1); 66 reverse(result.begin(),result.end()); 67 if(result.length()==0)result="0"; 68 return result; 69 } 70 string sum(string s1,string s2) 71 { 72 if(s1.length()<s2.length()) 73 { 74 string temp=s1; 75 s1=s2; 76 s2=temp; 77 } 78 int i,j; 79 for(i=s1.length()-1,j=s2.length()-1;i>=0;i--,j--) 80 { 81 s1[i]=char(s1[i]+(j>=0?s2[j]-'0':0)); //注意细节 82 if(s1[i]-'0'>=10) 83 { 84 s1[i]=char((s1[i]-'0')%10+'0'); 85 if(i) s1[i-1]++; 86 else s1='1'+s1; 87 } 88 } 89 return s1; 90 } 91 92 void cal64() 93 { 94 ans[0]="1"; 95 for(int i=1; i<=k; i++) 96 ans[i]=pre[i]; 97 string tmp= sum(ans[k], ans[k]); 98 ans[k+1]=_minus(tmp,"1"); 99 100 for(int i=k+2; i<=n; i++) 101 { 102 tmp=sum( ans[i-1], ans[i-1] ); 103 ans[i]=_minus(tmp,ans[i-k-2]); 104 } 105 cout<<_minus(pre[n], ans[n])<<endl; 106 107 } 108 109 void cal63() 110 { 111 a[0]=1; 112 for(int i=1; i<=k; i++) //小于k的情况,直接2^k 113 a[i]=a[i-1]+a[i-1]; 114 115 a[k+1]=a[k]+a[k]-1; 116 for(int i=k+2; i<=n; i++) 117 a[i] = a[i-1]-a[i-k-2]+a[i-1]; 118 119 cout<<((long long)1<<n)-a[n]<<endl; 120 } 121 void init() 122 { 123 string tmp; 124 ans.resize(103,tmp); 125 pre.resize(103,tmp); 126 pre[0]="1"; 127 for(int i=1; i<103; i++) 128 pre[i]=sum(pre[i-1], pre[i-1]); 129 } 130 int main() 131 { 132 //freopen("input.txt","r",stdin); 133 init(); 134 while(cin>>n>>k) 135 { 136 137 if(n==k) 138 { 139 cout<<"1"<<endl; 140 continue; 141 } 142 if(n==k+1) 143 { 144 cout<<"3"<<endl; 145 continue; 146 } 147 if(k==1) 148 { 149 cout<<_minus(pre[n],"1")<<endl; 150 continue; 151 } 152 k--; 153 if(n<62) //这里对抛62次以下的情况用longlong直接干掉。 154 cal63(); 155 else //大于62就用大数来算 156 cal64(); 157 158 } 159 return 0; 160 }