上次一次写的有点长了,保存好半天不成功,这次短一点吧,5道题目。
题目链接地址:http://acm.hdu.edu.cn/problemclass.php?id=516
ProID 1081 To The Max
也算是一道比较简单的题目了,这个除了用暴力法外,比较经典的做法是DP,这个其实也是那个最大子序列和的一个变种。来看看为什么。题目大意是,给一个二维数组,让你求一个和最大的子矩阵。
比如说求这个矩阵的最大子序列和:
0 -2 -7 0
9 2 -6 2
对于第一行来说,其实就是求一个连续子序列之和,对于两行来说,把他们加起来得到序列 9,0,-13,2,同样也是求最大子序列和。好了问题解决了。
代码如下:
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; int n; int data[101][101]; int sumdata[101]; int main() { while(scanf("%d",&n)!=EOF) { for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) scanf("%d",&data[i][j]); int maxNum=-100000; for(int i=1;i<=n;i++)//行递增 { memset(sumdata,0,sizeof(sumdata)); for(int j=i;j<=n;j++)//高度从i到n的矩阵 {//当前行的所有列相加,然后求最长子序列和,记录最大值 for(int k=1;k<=n;k++) sumdata[k]+=data[j][k]; int maxTemp=-100000; int sum = 0; for(int k=1;k<=n;k++) { sum += sumdata[k]; maxTemp = max(sum,maxTemp); sum = sum>0?sum:0; } maxNum=max(maxNum,maxTemp); } } printf("%d\n",maxNum); } return 0; }
题目大意是求一个最长上升子序列,在上一篇文章中已经讲过类似的问题了,不过是用二分查找做的,因为这道题目是要求输出序列之和,就得老老实实用原方法了。
代码如下:
#include<iostream> #include<string> #include<cstdio> #include<vector> #include<cctype> using namespace std; int data[1001]; int dp[1001]; int tmp[1001]; int main() { int n; while(scanf("%d",&n)!=EOF,n) { memset(dp,0,sizeof(dp)); memset(tmp,0,sizeof(tmp)); for(int i=0;i<n;i++) scanf("%d",&data[i]); dp[0]=data[0]; tmp[0]=0; int sum=0; for(int i=1;i<n;i++) { dp[i]=data[i]; for(int j=0;j<i;j++) { if(data[i]>data[j] && dp[i]<dp[j]+data[i]) dp[i]=dp[j]+data[i]; sum=max(sum,dp[i]); } } printf("%d\n",sum); } return 0; }
题目意思是给定一些硬币,有重量和价值,然后给一个一定重量的罐子,问在能否在装满罐子的条件下,猜到这个罐子的最小价值。这个是完全背包,在背包九讲中有详细的解释。也只是套个公式而已,需要注意初始化问题。
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; int t; int dp[10005]; struct _data { int a,b; }; _data data[501]; int main() { while(scanf("%d",&t)!=EOF) { int E,F,n; while(t--) { scanf("%d %d",&E,&F); scanf("%d",&n); memset(dp,0,sizeof(dp)); bool flag=false; for(int i=0;i<n;i++) { scanf("%d %d",&data[i].a,&data[i].b); if(data[i].b<F-E) flag=1; } if(!flag) printf("This is impossible.\n"); else { //初始化和01背包不一样 for(int i=1;i<=F-E;i++) dp[i]=5000000; //完全背包问题dp[i][j]表示考虑前i个物品在重量为j的情况下得到的最小价值 for(int i=0;i<n;i++) { for(int j=data[i].b;j<=F-E;j++) { dp[j]=min(dp[j],dp[j-data[i].b]+data[i].a); } } if(dp[F-E]!=5000000) printf("The minimum amount of money in the piggy-bank is %d.\n",dp[F-E]); else printf("This is impossible.\n"); } } } return 0; }
ProID 1121 Complete the Sequence
题目大意是,给顶你一个符合P(n)=P(n) = aD.n^D+aD-1.n^D-1+...+a1.n+a0 多项式的序列,然后让你续填m个序列,说白了就是找出一个序列的规律来,就是按规律填数。
咋一看之下好像无从下手,a(*)怎么找啊,n如何确定?其实吧多项式相减就可以看出规律来,因为这个多项式是比较简单的相邻数相减就可消去高次方,然后再减可以消去下一个高次方。例子如下:
对于序列 1,2,3,4,7来说,
0 | 1 | 2 | 4 | 7 |
1 | 1 | 2 | 3 | -- |
2 | 1 | 1 | -- | -- |
3 | 0 | -- | -- | -- |
表格中描述的是3次相减之后的情况,总是可以在最后一步(也就是第n-1次相减中变为一个数字)现在来计算序列之后的数字,从下往回推:
3 | 0 | 0 | -- | -- | -- |
2 | 1 | 1 | 1 | -- | -- |
1 | 1 | 2 | 3 | 4 | -- |
0 | 1 | 2 | 4 | 7 | 11 |
代码如下:
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; int n,m,t,s[105][105]; int main() { while(scanf("%d",&t)!=EOF) { while(t--) { scanf("%d %d",&n,&m); for(int i=0;i<n;i++) scanf("%d",&s[0][i]); for(int i=1;i<n;i++)//计算n阶差值 for(int j=0;i+j<n;j++) s[i][j]=s[i-1][j+1]-s[i-1][j]; for(int i=1;i<=m;i++) s[n-1][i]=s[n-1][0]; //还原过程 for(int i=n-2;i>=0;i--) for(int j=0;j<m;j++) s[i][n-i+j]=s[i][n-i+j-1]+s[i+1][n-i+j-1]; //输出m需要求的值 for(int i=0;i<m-1;i++) printf("%d ",s[0][n+i]); printf("%d\n",s[0][n+m-1]); } }return 0; }
题目大意是老板要雇一群人工作,雇人和解雇都要付工资,工作的时候要付薪水,而且不工作也要付(真好啊 )。问你帮忙计算出完成一项工作所需的最小花费。
这道题目很类似于前一篇文章中的星河战队问题,只不过这个比那道题目稍微简单一些,因为是线性的,不是树形的。
状态转移方程如下:
Dp[i][j]为前i个月的留j个人的最优解; 其中 Num[i]<=j<=Max{Num[i]}; Num 数组表示的是当前这个月所需的最少人数,当然如果所雇用的人数绝对不超过所有月份中所多的人数,否则白白养那么一群人,老板也太好了。
对于Num[i]<=k<=Max_n, 当k<j时, 招聘;当k>j时, 解雇 然后求出最小值。Dp[i][j]=min{Dp[i-1][k…Max_n]+(招聘,解雇,工资);
代码如下:
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; int dp[13][1005],num[13]; int n; int hire,fire,salary; int main() { while(scanf("%d",&n),n) { int MaxNum=0; scanf("%d%d%d",&hire,&salary,&fire); //如果说其中某个月所需要的人最多,那么总的人数不会超过这个,否则就是这个老板太慈善了。 for(int i=0;i<n;i++) { scanf("%d",&num[i]); MaxNum=max(MaxNum,num[i]); } memset(dp,0,sizeof(dp)); for(int i=num[0];i<=MaxNum;i++) { dp[1][i]=i*(salary+hire); //hire 也要加上 } for(int i=2;i<=n;i++) { for(int j=num[i-1];j<=MaxNum;j++) { int Min=0xffffff; for(int k=num[i-2];k<=MaxNum;k++)//上个月雇佣的人数 { //当k<j时, 招聘, 当k>j时, 解雇, 然后求出最小值 if(k>j) Min=min(Min,dp[i-1][k]+(k-j)*fire+j*salary); else Min=min(Min,dp[i-1][k]+(j-k)*hire+j*salary); } dp[i][j]=Min; } } int ans=0xffffff; for(int i=num[n-1];i<=MaxNum;i++) ans=min(ans,dp[n][i]); printf("%d\n",ans); } return 0; }