一本通的题
一、昆虫繁殖
#include<iostream>
using namespace std;
//昆虫繁殖
int main(){
long long a[101]={0},b[101]={0},i,j,x,y,z;
cin>>x>>y>>z;
for(i=1;i<=x;i++) {a[i]=1;b[i]=0; //成虫和幼虫
}
for(i=x+1;i<=z+1;i++){ //注意下标,从x+1开始到第z+1个月
b[i]=y*a[i-x];//b[i]表示这个月的幼虫
a[i]=a[i-1]+b[i-2]; //表示成虫
}
cout<<a[z+1]<<endl;
return 0;
}
二、位数问题
n位数中,有偶数个3的个数
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
int n,a[1001][2];
//这道题的思想,用递推,从上一位数的结果退出这一位的结果
//
int f=9;
int main(){
cin>>n;
a[1][0]=9;//0也是偶数!!
a[1][1]=1;
for(int i=2;i<=n;i++){
if(i==n) f=8; //最后一位的取值再1-2, 4-9之间
a[i][0]=(a[i-1][0]*f+a[i-1][1])%12345; //注意f的位置 //偶数个
a[i][1]=(a[i-1][1]*f+a[i-1][0])%12345; //奇数个
}
cout<<a[n][0]<<endl;
return 0;
}
三、经典的放苹果问题
把n个同样的苹果放在m个同样的盘子里,允许有的盘子空着不放,有多少种方法?-------分为有无空盘
(1)边界条件:n=0或者m=1 一种方法
(2)没有空盘,那么先给每个盘子分配一个,所以有a[n-m][m]种,有一个空盘,有a[n][m-1]种
(3)ps.如果n<m 那个结果为a[n][n]
for(int n=0;n<=10;n++){//苹果
for(int m=0;m<=10;m++){//盘子
if(n==0||m==1) a[n][m]=1; //只有一个盘子或者0个苹果都是一种情况
else if(n>=m) a[n][m]=a[n][m-1]+a[n-m][m]; //分为有无空盘
else a[n][m]=a[n][n];
}
}
数的划分(其实就是n=m的放苹果问题)
可以递推,也可以动态规划,是十分经典的题
1、递推
用f[n][m]表示在值为n的情况,最大划分为m的值(每个盘子都放一个),根据n和m的关系,考虑以下几种情况:
(1)当n=1时,不论m的值为多少(m>0),只有一种划分即{1};
(2) 当m=1时,不论n的值为多少,只有一种划分即n个1,{1,1,1,…,1};
(3) 当n=m时,根据划分中是否包含n,可以分为两种情况:
(a). 划分中包含n的情况,只有一个即{n};
(b). 划分中不包含n的情况,这时划分中最大的数字也一定比n小,即n的所有(n-1)划分。 因此 f(n,n) =1 + f(n,n-1);
(4) 当n< m时,由于划分中不可能出现负数,因此就相当于f(n,n);
(5) 但n>m时,根据划分中是否包含最大值m,可以分为两种情况:
(a). 划分中包含m的情况,即{m, {x1,x2,...xi}}, 其中{x1,x2,... xi} 的和为n-m,因此这种情况下为f(n-m,m)
(b). 划分中不包含m的情况,则划分中所有值都比m小,即n的(m-1)划分,个数为f(n,m-1);因此 f(n, m) = f(n-m, m)+f(n,m-1);
综上所述:
f(n,m)=1; (n=1 or m=1)
f(n,m)=f(n, n); (n<m)
f(n,m)=1+ f(n, m-1); (n=m)
f(n,m)=f(n-m,m)+f(n,m-1); (n>m)
二、动态规划
状态转移方程:
当 n == 1 || k == 1 时,f(n, k) == 1, n为1,那么只能为1; 而k为1,那么只能划分成n个1.
当 n < k 时,f(n, k) == f(n, n),因为n的划分中不可能出现比n大的数,所以可以将最大值从k降到n;
当 n >= k 时,f(n, k) = f(n-k, k) + f(n, k-1), 前半部分是划分中存在最大值k,所以可以在(n-k)中继续以最大值为k来划分,而后半部分则是划分中最大值不是k,那么其结果和以(k-1)为最大值的划分是一样的。在初始化中可以将f(0, k)初始化为1,及对应f(n, n)可能出现的情况。
综合来说,使用递归更加容易理解,不过使用动态规划时间消耗更小。
#include<iostream>
using namespace std;
long long d[125][125];
void dp()
{
for(int i=1;i<=120;i++)
d[i][1]=d[1][i]=d[0][i]=1;
for(int i=2;i<=120;i++)
for(int j=2;j<=120;j++)
{
if(i<j)
d[i][j]=d[i][i];
else
d[i][j]=d[i-j][j]+d[i][j-1];
}
}
int main()
{
int n;
dp();
while(cin>>n)
cout<<d[n][n]<<endl;
return 0;
}
四、 判断整除

将前n-1个数看成一个整体序列,则n个数可能产生的结果就等于“前n-1个数产生的所有结果分别加上第n个数或分别减去第n个数”
n-2也同理……直到1为止。
由于是判断有无结果能被k整除,所以所有的中间结果都可以取余k,从而使所有可能的结果保持在一个不大的范围,用数组进行存储。
下面代码中的ans[i][j]表示i个数的序列有没有取余k后为j的结果,0表示没有,1表示有。
这道题不如过说是逻辑和取余运算的例题了。。。。
//判断整除
//哎
int a[10001][101]; //记录计算结果和过程的
int n,k;
int b[10001];//记录数组的
int main(){
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>b[i];
b[i]%=k; //这一步!!!
}
//初始化
memset(a,0,sizeof(a));
a[1][b[1]]=1;
a[1][-b[1]+k]=1;
for(int i=1;i<n;i++){ //小于n,因为最后会算到n
for(int j=0;j<k;j++){
if(a[i][j]){
a[i+1][(j+b[i+1])%k]=1; //注意这里都是a[i+1] 还有 b[i+1]
a[i+1][(j+(-b[i+1]+k))%k]=1;
}
}
}
if(a[n][0]==1) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
五、山区建小学
cin>>n>>m;
memset(a,0,sizeof(a));
for(int i=1;i<n;i++){ //注意次数是输入n-1个数
cin>>dis[i][i+1];
}
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
dis[i][j]=dis[j-1][j]+dis[i][j-1]; //
dis[j][i]=dis[i][j];
}
}
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
int mid=(i+j)/2;
a[i][j]=0;
for(int k=i;k<=j;k++) a[i][j]+=dis[k][mid]; //在两地间建小学最近,然后用a[i][j]存储在i和j中间建小学的最短距离
}
}
for(int i=1;i<=n;i++) dp[i][1]=a[1][i];
//注意理解这段话的意思,前i个村庄建1个学校和在1和村庄i中间建一个是等价的
for(int i=1;i<=n;i++){
for(int j=2;j<=m;j++){ //注意j从多少开始
dp[i][j]=0xf7fffffff;
for(int k=j-1;k<=i;k++){ //a[][]数组其实用了贪心的思想
//再前k个村庄里面建j-1个学校,再k+1--i之间再建一所学校,建在“中间”
dp[i][j]=min(dp[i][j],dp[k][j-1]+a[k+1][i]); //k在枚举i与j之间的村庄
}
}
}
cout<<dp[n][m]<<endl;
六、流感传染,这道题需要用两个数组,保存,一个保存今天的,昨天的,因为今天被感染的只能从明天开始感染别人
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++) {
cin>>a[i][j];
aa[i][j]=a[i][j];
}
int m;cin>>m;
for(int day=1;day<=m;day++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++)
if(a[i][j]=='@') bf(i,j);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++) a[i][j]=aa[i][j]; //注意这里的赋值顺寻
P2696 慈善的约瑟夫
实在是看不懂递推。。。用模拟做的
#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
//新约瑟夫问题,真的看不懂递推,只懂模拟、、、
int a[100005];
int num,f,n,s,x;
int main(){
cin>>n;
while(1){ //一直算到最后的胜利者就是编号最大的
num=n;
x=f=0;
memset(a,0,sizeof(a)); //表示有没有出队
while(num!=1){
x++;
if(x>n) x-=n;
if(a[x]!=0) continue;
f++;
if(f==2){ //交替出队
f=0;
a[x]=1;//出队了
num--;
}
}
for(int i=1;i<=n;i++){
if(a[i]==0){
if(i==n){
cout<<s+2*i<<endl;
return 0;
}
//如果不是的话,就处理这一轮的结果,把编号大于胜利者的 都踢出去
s=s+n-i; //出队的给一块
n=i; //剩下的人就只剩i个
break;
}
}
}
return 0;
}
【典型的递推关系】
1、Fibonacci数列
2、汉诺塔问题: hn=2*h(n-1)+1
3、平面分割问题:设有n条封闭曲线画在平面上,任何两条封闭曲线恰好交于两点,且任何三条封闭曲线不相交于同一点,问把平面分隔成多少个
h(n)-h(n-1)=2*(n-1)
4、catalan数:凸多边形的对角三角形剖分的个数 :组合数学
5、第二类stirling数:n个有区别的球放到m个没区别的盒子里,求放法
考虑第n个球:单独放一个盒子:S(n-1,m-1)
与别的球共占一个盒子:m*S(n-1,m)
所以方法是S(n,m)=S(n-1,m-1)+m*S(n-1,m) n>1,m>=1 边界条件为S(n,0)=0 S(n,1)=1 S(n,n)=1 S(n,k) =0(k>n)
posted on
浙公网安备 33010602011771号