第一题:数塔 HDU - 2084
做法:
从第 i , j 个 节点往下走的最优解可以由从第 i+1,j 个节点往下走的最优解和第i+1,j+1个节点往下走的最优解得出,二者取其优即可。
代码:
记忆化搜素
1 #include<iostream> 2 using namespace std; 3 #include<cstdio> 4 using namespace std; 5 int n; 6 int f[100][100]; 7 int v[100][100]; 8 int a[100][100]; 9 int fget(int x,int y)//记忆化搜索求值 10 { 11 if (x<=0||y<=0) return 0;//如果超出范围返回0 12 if (x>n||y>x) return 0;//如果超出范围返回0 13 if (v[x][y]) return f[x][y];//如果之前访问过那么直接返回之前访问过的值 14 v[x][y]=1;//标记访问 15 int max=-10000000;//max为最大值 初始化一个很小的数 16 if (!v[x+1][y])//如果之前没有递归过 x+1,y 的 值 17 { 18 f[x+1][y]=fget(x+1,y);//递归获得答案 19 } 20 if (!v[x+1][y+1])//如果之前没有递归过 x+1,y+1 的 值 21 { 22 f[x+1][y+1]=fget(x+1,y+1);//递归获得答案 23 } 24 25 //f[x][y]的值可以由 f[x+1][y] + a[x][y]得出 26 f[x][y]=f[x+1][y]+a[x][y]; 27 28 //当然, f[x][y]的值也可以由 f[x+1][y+1] + a[x][y]得出 29 //那么我们就取两者中较大的那个 30 if (f[x][y]<=f[x+1][y+1]+a[x][y]) f[x][y]=f[x+1][y+1]+a[x][y]; 31 32 return f[x][y];//返回答案 33 } 34 void deal() 35 { 36 int i; 37 int j; 38 cin>>n; 39 //读取 + 初始化 40 for (i=1;i<=n;i++) 41 for (j=1;j<=i;j++) 42 { 43 f[i][j]=0; 44 scanf("%d",&a[i][j]); 45 v[i][j]=0; 46 f[i][j]=a[i][j]; 47 } 48 int max=-1; 49 50 //fget(1,1)也就是数组顶部的元素也就是答案 51 cout<<fget(1,1)<<endl; 52 53 } 54 int main() 55 { 56 int n; 57 cin>>n;//n组数据 58 while (n--) 59 { 60 deal();//逐个处理 61 } 62 return 0; 63 }
递推代码:
1 #include<iostream> 2 #include<algorithm> 3 #include<cstring> 4 using namespace std; 5 6 int main() { 7 int a[105][105]; 8 int t, n, i, j; 9 while(cin>>t) { 10 while(t--) { 11 cin>>n; 12 memset(a,0,sizeof(a)); 13 for(i=0; i<n; i++) 14 for(j=0; j<=i; j++) 15 cin>>a[i][j]; 16 for(i=n-1; i>=0; i--) 17 for(j=0; j<=i; j++) 18 a[i][j]=a[i][j]+max(a[i+1][j],a[i+1][j+1]); 19 cout<<a[0][0]<<endl; 20 } 21 } 22 return 0; 23 }
第二题:超级楼梯
来源:HDU - 2041
转态转移方程 f[i]=f[i-1]+f[i-2]
代码:
记忆化搜索
1 #include<iostream> 2 using namespace std; 3 #include<cstring> 4 #include<cstdio> 5 int dp[50]; 6 int dfs(int x){//递归得到x级楼梯的走法数量 7 if(x<0) return 0;//小于0级台阶当然是0 8 if(dp[x]>0)//已经有答案 9 return dp[x]; 10 else 11 //该级台阶的答案可以由i-1阶台阶的答案和i-2阶的答案求和得到 12 return dp[x]=dfs(x-1)+dfs(x-2); 13 } 14 int main(){ 15 int t; 16 memset(dp,0,sizeof(dp)); 17 dp[0]=1; 18 scanf("%d",&t); 19 while(t--){ 20 int d; 21 scanf("%d",&d); 22 printf("%d\n",dfs(d-1)); 23 } 24 return 0; 25 }
递推:
1 #include<iostream> 2 using namespace std; 3 int main(){ 4 int n; 5 cin>>n; 6 for(int j=0;j<n;j++){ 7 int m; 8 cin>>m; 9 int a[45]; 10 for(int i=2;i<=m;i++){ 11 a[2]=1; 12 a[3]=2; 13 if(i>3) 14 a[i]=a[i-1]+a[i-2]; 15 } 16 cout<<a[m]<<endl; 17 } 18 }
第三题
母牛的故事
来源:HDU - 2018
第一年只有一开始的牛
第二年-第四年只有一开始的牛生了小牛
第五年第二年出生的小牛也生了小牛
第六年第三年出生的小牛也可以生母牛了
也就是说第i年时,3年前出生(包括第 前三年)的母牛 都有生育能力
那么设dp[i]为第i年的牛的数量
那么,显然dp[i-1]为没生小牛之前的牛的数量
dp[i-3]为3年前的牛的数量,也就是有生育能力的小牛的数量
那么状态转移方程就可以得到:
dp[i]=dp[i-1]+dp[i-3]
代码:
1 #include<iostream> 2 using namespace std; 3 #include<cstdio> 4 #include<cstring> 5 int f[60];//第n年牛的数量 6 int main(){ 7 for(int i=1;i<=4;i++) 8 f[i]=i; 9 10 //f[i-1]为去年牛的数量 f[i-3]为可以生牛的母牛的数量 也是新生的小牛的数量 11 for(int i=5;i<=55;i++) 12 f[i]=f[i-1]+f[i-3]; 13 int n; 14 while(scanf("%d",&n)!=EOF&&n){//循环读入直到文件尾部或者n为0时结束 15 printf("%d\n",f[n]); 16 } 17 return 0; 18 }
第四题:
来源:
题目大意:
已知N个糖果的重量和价值. 我们有一个口袋, 最多可以装V重量的糖果. 问口袋最多能放多少价值的糖果进去? Input
Output
对每一组数据, 输出口袋最终可以放进去糖果的价值.
Sample Input
1 5 10 1 2 3 4 5 5 4 3 2 1
Sample Output
14
做法:
一个简单的01背包问题
解决0 1 背包问题的思想看背包九讲
题解参见代码注释
1 #include<iostream> 2 using namespace std; 3 #include<cstring> 4 #include<cstdio> 5 void deal(); 6 int main(){ 7 int t; 8 scanf("%d",&t); 9 while(t--)//t组数据 10 deal(); 11 return 0; 12 } 13 int *value,*weight,*f; 14 void deal(){ 15 int N,V; 16 scanf("%d%d",&N,&V); 17 value=new int[N];//每个糖果重量数组 18 weight=new int[N];//每个糖果价值数组 19 for(int i=0;i<N;i++) 20 scanf("%d",value+i); 21 for(int i=0;i<N;i++) 22 scanf("%d",weight+i); 23 f=new int[V+1]; 24 memset(f,0,sizeof(int)*(V+1));//初始化 25 for(int i=0;i<N;i++)//对于第i件物品 26 for(int j=V;j>=0;j--)//当空间为j的时候 27 if(j-weight[i]<0)//如果空间j放不下第i件物品 28 continue;//那就不管它好了 29 else 30 f[j]=max(f[j],f[j-weight[i]]+value[i]);//要么把f[j]本身和放下第i件物品比一下看哪个大 31 int ans=0; 32 for(int i=0;i<=V;i++) 33 ans=max(ans,f[i]);//答案取最大的那个 34 printf("%d\n",ans); 35 return; 36 }
第五题:
Common Subsequence
题解:
第一个字符串的前 i 个字符 和 第二个字符串 的 前 j 个字符 的最长公共子串 可以由
第一个字符串的前 i-1 个字符 和 第二个字符串的前j个字符 的最长公共子串
第一个字符串的前 i 个字符 和 第二个字符串的前j-1个字符 的最长公共子串
如果第一个字符串的第i个字符和第二个字符串的第j个字符相同
第一个字符串的前 i 个字符 和 第二个字符串 的 前 j 个字符 的最长公共子串 也可以由
第一个字符串的前i-1个字符和第二个字符的前j-1个字符的最长公共子串的得到
代码:记忆化搜索
1 #include<iostream> 2 using namespace std; 3 #include<cstdio> 4 #include<cstring> 5 const int maxn=1010; 6 char *s1,*s2; 7 int **dp; 8 9 int dfs(int x,int y){//求s1的前x个字符与s2的前y个字符 10 int ans=0; 11 if(x<0||y<0)//如果x或y中有一个超出字符串边界 12 return 0; //那么返回0 13 if(dp[x][y]>=0)//如果之前之前已经递归过此种状态,直接返回 14 return dp[x][y]; 15 if(s1[x]==s2[y])//如果s1的第x个字符与s2的第y个字符相等 16 ans=max(dfs(x-1,y-1)+1,ans); 17 ans=max(ans,dfs(x-1,y));//递归查找求s1的前x-1个字符与s2的前y个字符 18 ans=max(ans,dfs(x,y-1));//递归查找求s1的前x个字符与s2的前y-1个字符 19 return dp[x][y]=ans;//记录此状态的值并返回 20 } 21 22 int main(){ 23 s1=new char[maxn];//声明内存 24 s2=new char[maxn]; 25 while(scanf("%s%s",s1,s2)!=EOF){ 26 27 int len1,len2; 28 len1=strlen(s1);//获取字符串长度 29 len2=strlen(s2); 30 31 //声明数组 32 dp=new int*[len1+1]; 33 for(int i=0;i<=len1;i++) 34 dp[i]=new int[len2+1]; 35 36 //初始化数组 37 for(int i=0;i<=len1;i++) 38 for(int j=0;j<=len2;j++) 39 dp[i][j]=-1; 40 41 printf("%d\n",dfs(len1-1,len2-1)); 42 43 //释放内存 44 for(int i=0;i<=len1;i++) 45 delete dp[i]; 46 delete dp; 47 delete s1; 48 delete s2; 49 50 s1=new char[maxn];//重新声明 51 s2=new char[maxn]; 52 } 53 return 0;}
递推:
#include <iostream> #include<cstring> using namespace std; int dp[2001][2001]; int main() { char a[2001],b[2001]; while(cin>>a>>b) { int i,j; int al,bl; al=strlen(a); bl=strlen(b); for(i=0; i<=al; i++) dp[i][0]=0; for(i=0; i<=bl; i++) dp[0][i]=0; for(i=1; i<=al; i++) for(j=1; j<=bl; j++) { if(a[i-1]==b[j-1]) dp[i][j]=dp[i-1][j-1]+1; else dp[i][j] = dp[i-1][j] > dp[i][j-1] ? dp[i-1][j] : dp[i][j-1]; } cout<<dp[al][bl]<<endl; } return 0; }
第六题:
最少拦截系统
一种思路是
贪心去求解
把每一个导弹和它之前发射的所有子弹的拦截系统的高度去比较
如果有比它高的,那么贪心地去选择最低的那个可行的拦截系统
如果没有比它高的拦截系统,新建一个拦截系统答案+1
第二种思路就是把问题转化为最长递增子序列的问题
和第K题解法相同
第七题:
来源: HDU - 1114
题目大意:
在 ACM 能够开展之前,必须准备预算,并获得必要的财力支持。该活动的主要收入来自于 Irreversibly Bound Money (IBM)。思路很简单。任何时候,某位 ACM 会员有少量的钱时,他将所有的硬币投入到小猪储钱罐中。这个过程不可逆,因为只有把小猪储钱罐打碎才能取出硬币。在足够长的时间之后,小猪储钱罐中有了足够的现金,用于支付 ACM 活动所需的花费。
但是,小猪储钱罐存在一个大的问题,即无法确定其中有多少钱。因此,我们可能在打碎小猪储钱罐之后,发现里面的钱不够。显然,我们希望避免这种不愉快的情况。唯一的可能是,称一下小猪储钱罐的重量,并尝试猜测里面的有多少硬币。假定我们能够精确判断小猪储钱罐的重量,并且我们也知道给定币种的所有硬币的重量。那么,我们可以保证小猪储钱罐中最少有多少钱。
你的任务是找出最差的情形,即判断小猪储钱罐中的硬币最少有多少钱。我们需要你的帮助。不能再贸然打碎小猪储钱罐了!
输入
输入包含 T 组测试数据。输入文件的第一行,给出了 T 的值。
对于每组测试数据,第一行包含 E 和 F 两个整数,它们表示空的小猪储钱罐的重量,以及装有硬币的小猪储钱罐的重量。两个重量的计量单位都是 g (克)。小猪储钱罐的重量不会超过 10 kg (千克),即 1 <= E <= F <= 10000 。每组测试数据的第二行,有一个整数 N (1 <= N <= 500),提供了给定币种的不同硬币有多少种。接下来的 N 行,每行指定一种硬币类型,每行包含两个整数 P 和 W (1 <= P <= 50000,1 <= W <=10000)。P 是硬币的金额 (货币计量单位);W 是它的重量,以 g (克) 为计量单位。
输出
对于每组测试数据,打印一行输出。每行必须包含句子 “The minimum amount of money in the piggy-bank is X.” 其中,X 表示对于给定总重量的硬币,所能得到的最少金额。如果无法恰好得到给定的重量,则打印一行 “This is impossible.” 。
解法:
一个完全背包问题,每个物品有无限种,问最优解
参见背包九讲,完全背包问题
代码:
1 #include<iostream> 2 using namespace std; 3 #include<cstdio> 4 int f[101000],a[10000],v[10000]; 5 int main() 6 { 7 int n; 8 int t; 9 cin>>t; 10 int l,r,fv; 11 while(t--) 12 { 13 scanf("%d%d",&l,&r); 14 fv=r-l; 15 cin>>n; 16 for (int i=1;i<=n;i++) 17 scanf("%d%d",&a[i],&v[i]); 18 for (int i=1;i<=fv;i++)//f[i]储存 总共i大小的重量 的 最小金额 19 f[i]=0x7fffffff/2; //初始值赋个很大的值 20 f[0]=0; 21 for (int i=1;i<=n;i++)//对于第i种硬币 22 { 23 for (int j=v[i];j<=fv;j++) //对于j大小的空间 24 { 25 f[j]=min(f[j],f[j-v[i]]+a[i]); //f[j]的值为f[j]和f[j-v[i]]较小的那个值 26 } 27 } 28 if (f[fv]==0x7fffffff/2) //如果依然是最大值,那么就是不可能 29 cout<<"This is impossible."<<endl; 30 else //否则输出答案 31 printf("The minimum amount of money in the piggy-bank is %d.\n",f[fv]); 32 } 33 return 0; 34 }