【C】动态规划总结

问题 A: 第二题

时间限制: 1 Sec   内存限制: 32 MB
提交: 37   解决: 0
[ 提交][ 状态][ TK题库][命题人: ]

题目描述

一个数组中有若干正整数,将此数组划分为两个子数组,使得两个子数组各元素之和a,b的差最小,对于非法输入应该输出ERROR。

输入

数组中的元素

输出

降序输出两个子数组的元素和

样例输入

10 20 30 10 10
10 20 abc 10 10

样例输出

40 40
ERROR

//01背包问题
#include<stdio.h>
#include<iostream>
#include<set>
#include<algorithm>
#include<string>
#include<string.h>
#include<sstream>
using namespace std;
int buf[10000000]={0};
int dp[10000000]={0};
bool cmp(int a,int b){
	return a>b;
}
bool ifvalid(string a){
	int len=a.size();
	int i;
	for(i=0;i<len;i++){
		if(a[i]<'0'||a[i]>'9') return false;
	}
	return true;
}
void deal(string &str,int &flag,int &i,int &sum){
	string strr;
	while(str.find(" ")!=string::npos){
		strr=str.substr(0,str.find(" "));
		if(ifvalid(strr)==false){
			flag=1;
			return;
		}
		else{
			buf[i]=atoi(strr.c_str());
			sum+=buf[i++];
			str.erase(0,str.find(" ")+1);
		}
	}
	if(ifvalid(str)==false){
		flag=1;
		return;
	}
	else{
		buf[i]=atoi(str.c_str());
		sum+=buf[i++];
	}
	return;
}
int main(){
	int i,j,k;
	string str;
	while(getline(cin,str)!=NULL){
		i=1;
		int flag=0;
		int sum=0;
		memset(buf,0,sizeof(buf));
		memset(dp,0,sizeof(dp));
		deal(str,flag,i,sum);
		if(flag==1) cout<<"ERROR"<<endl;
		else{
			int sum2=sum/2;
			for(j=1;j<i;j++){//01背包
				//cout<<buf[j]<<endl;
				for(k=sum2;k>=buf[j];k--){
					dp[k]=max(dp[k],dp[k-buf[j]]+buf[j]);
				}
			}//从i-1个数字中挑选若干个,使其和不超过sum2,且尽量大
			cout<<dp[sum2]<<" "<<sum-dp[sum2]<<endl;
			/*int sum1=0;
			int sum2=0;
			if(i==1) cout<<buf[0]<<endl;
			else if(i>1){
				 sum1=buf[0];
				 sum2=buf[1];
				 for(int j=2;j<i;j++){
					 if(sum1+buf[j]-sum2<sum2+buf[j]-sum1){
						 sum1+=buf[j];
						 //printf("1.buf[%d]=%d\n",j,buf[j]);
					 }
					 else{
						 //j2++;
						 sum2+=buf[j];
						 //printf("2.buf[%d]=%d\n",j,buf[j]);
					 }
				 }
				 if(sum1<sum2) cout<<sum1<<" "<<sum2<<endl;
				 else cout<<sum2<<" "<<sum1<<endl;
			}*///注释掉的部分内存超限
		}		
	}
	return 0;

}

问题 B: 拦截导弹

时间限制: 1 Sec   内存限制: 32 MB
提交: 44   解决: 25
[ 提交][ 状态][ TK题库][命题人: ]

题目描述

某国为了防御敌国的导弹袭击,开发出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭,并观测到导弹依次飞来的高度,请计算这套系统最多能拦截多少导弹。拦截来袭导弹时,必须按来袭导弹袭击的时间顺序,不允许先拦截后面的导弹,再拦截前面的导弹。

输入

每组输入有两行,第一行,输入雷达捕捉到的敌国导弹的数量k(k<=25),第二行,输入k个正整数,表示k枚导弹的高度,按来袭导弹的袭击时间顺序给出,以空格分隔。


输出

每组输出只有一行,包含一个整数,表示最多能拦截多少枚导弹。



样例输入

4
9 6 7 8
7
4 5 6 7 13 42 3
5
6 5 4 3 5
0

样例输出

2
2
4

//最长不上升子序列
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
int main(){
	int n,i,j;
	cin>>n;
	while(n!=0){
		int a[30];
		int dp[30];
		for(i=1;i<=n;i++){
			cin>>a[i];
		}
		int ans=-1;
		for(i=1;i<=n;i++){
			dp[i]=1;
			for(j=1;j<i;j++){
				if(a[j]>=a[i]&&dp[j]+1>dp[i]){
					dp[i]=dp[j]+1;
				}
			}
			ans=max(dp[i],ans);
		}
		cout<<ans<<endl;
		cin>>n;
	}
	return 0;
}

问题 C: 合唱队形

时间限制: 1 Sec   内存限制: 32 MB
提交: 27   解决: 16
[ 提交][ 状态][ TK题库][命题人: ]

题目描述

N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学不交换位置就能排成合唱队形。
合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1, 2, …, K,他们的身高分别为T1, T2, …, TK,
则他们的身高满足T1 < T2 < … < Ti , Ti > Ti+1 > … > TK (1 <= i <= K)。
你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

输入

输入的第一行是一个整数N(2 <= N <= 100),表示同学的总数。
第一行有n个整数,用空格分隔,第i个整数Ti(130 <= Ti <= 230)是第i位同学的身高(厘米)。

输出

可能包括多组测试数据,对于每组数据,
输出包括一行,这一行只包含一个整数,就是最少需要几位同学出列。

样例输入

3
174 208 219 
6
145 206 193 171 187 167 
0

样例输出

0
1
//从i向左最长递增(不是不下降)子序列,从i向右最长递减(不是不上升)子序列
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
int main(){
	int i,n,j;
	cin>>n;
	while(n!=0){
		int a[110];
		int dp1[110],dp2[110];
		for(i=1;i<=n;i++){
			cin>>a[i];
		}
		int ans1=-1,ans2=-1;
		//先求最长不下降
		for(i=1;i<=n;i++){
			dp1[i]=1;
			for(j=1;j<i;j++){
				if(a[i]>a[j]&&dp1[j]+1>dp1[i]){
					dp1[i]=dp1[j]+1;
				}
			}
			ans1=max(dp1[i],ans1);
		}
		//再求最长不上升
		for(i=n;i>=1;i--){
			dp2[i]=1;
			for(j=i+1;j<=n;j++){
				if(a[j]<a[i]&&dp2[j]+1>dp2[i]){
					dp2[i]=dp2[j]+1;
				}
			}
			ans2=max(dp2[i],ans2);
		}
		/*cout<<ans1<<" "<<ans2<<endl;
		for(i=1;i<=n;i++){
			printf("dp1[%d]=%d,dp2[%d]=%d\n",i,dp1[i],i,dp2[i]);
		}*/
		int summax=-1;
		int u=-1;
		for(i=1;i<=n;i++){
			if(dp1[i]+dp2[i]>summax){
				summax=dp1[i]+dp2[i];
				u=i;
			}
		}
		cout<<n-summax+1<<endl;
		cin>>n;
	}
	return 0;
}

问题 D: Coincidence

时间限制: 1 Sec   内存限制: 32 MB
提交: 24   解决: 9
[ 提交][ 状态][ TK题库][命题人: ]

题目描述

Find a longest common subsequence of two strings.

输入

First and second line of each input case contain two strings of lowercase character a…z. There are no spaces before, inside or after the strings. Lengths of strings do not exceed 100.

输出

For each case, output k – the length of a longest common subsequence in one line.

样例输入

google
goodbye

样例输出

4
//最长公共字串
//while(getline(cin,str1)!=NULL)的写法!!!
#include<stdio.h>
#include<iostream>
#include<string>
#include<sstream>
using namespace std;
int main(){
	int i,j;
	string str1,str2;
	while(getline(cin,str1)!=NULL){
		getline(cin,str2);
		//getline(cin,str1);
		//getline(cin,str2);
		str1=" "+str1;
		str2=" "+str2;
		int dp[110][110];
		for(i=0;i<str1.size();i++) dp[i][0]=0;
		for(i=0;i<str2.size();i++) dp[0][i]=0;
		for(i=1;i<str1.size();i++){
			for(j=1;j<str2.size();j++){
				if(str1[i]==str2[j]){
					dp[i][j]=dp[i-1][j-1]+1;
				}
				else{
					dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
				}
			}
		}
		cout<<dp[str1.size()-1][str2.size()-1]<<endl;
	}
	return 0;
}	

问题 E: 最大子矩阵

时间限制: 1 Sec   内存限制: 32 MB
提交: 21   解决: 9
[ 提交][ 状态][ TK题库][命题人: ]

题目描述

已知矩阵的大小定义为矩阵中所有元素的和。给定一个矩阵,你的任务是找到最大的非空(大小至少是1 * 1)子矩阵。
比如,如下4 * 4的矩阵

0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2

的最大子矩阵是

9 2
-4 1
-1 8

这个子矩阵的大小是15。

输入

输入是一个N * N的矩阵。输入的第一行给出N (0 < N <= 100)。
再后面的若干行中,依次(首先从左到右给出第一行的N个整数,再从左到右给出第二行的N个整数……)给出矩阵中的N2个整数,整数之间由空白字符分隔(空格或者空行)。
已知矩阵中整数的范围都在[-127, 127]。

输出

测试数据可能有多组,对于每组测试数据,输出最大子矩阵的大小。

样例输入

1
27 
3
-40 29 -16 
38 18 22 
24 -35 5 

样例输出

27
78
//求出每一行的最大连续和子序列
#include<stdio.h>
#include<iostream>
using namespace std;
struct SUM{
	int sh;//开始行
	int eh;//结束行
	int sl;//开始列
	int el;//结束列
	int data;
};
void deal(SUM d[],int i,int j,int n){
	for(int k=0;k<n;k++){
		d[k].sh=i;
		d[k].eh=j;
	}
}
int main(){
	int n,i,j,k;
	while(scanf("%d",&n)!=EOF){
		int buf[110][110];
		SUM dpp[10010];
		int pp=0;
		for(i=0;i<n;i++){
			for(j=0;j<n;j++){
				cin>>buf[i][j];
			}
		}
		for(i=0;i<n;i++){
			//i-j行压缩,并求最大连续和子序列
			int yasuo[110]={0};
			for(j=i;j<n;j++){
				for(k=0;k<n;k++){
					yasuo[k]+=buf[j][k];
				}
				SUM dp[110];
				dp[0].data=yasuo[0];
				deal(dp,i,j,n);//统一初始化开始行和结束行
				dp[0].sl=dp[0].el=0;
				for(k=1;k<n;k++){
					if(yasuo[k]+dp[k-1].data>=yasuo[k]){
						dp[k].data=yasuo[k]+dp[k-1].data;
						dp[k].sl=dp[k-1].sl;dp[k].el=k;
					}
					else{
						dp[k].data=yasuo[k];
						dp[k].sl=dp[k].el=k;
					}
				}
				int maxn=-12701;
				int u=-1;
				//printf("********%d-%d\n",i,j);
				for(k=0;k<n;k++){
					//cout<<dp[k].data<<endl;
					if(dp[k].data>maxn){
						maxn=dp[k].data;
						u=k;
					}
				}
				dpp[pp++]=dp[u];//记录这个从i-j行压缩而来的最大连续和
			}
		}
		int maxn=-12701;
		int u=-1;
		for(i=0;i<pp;i++){
			if(dpp[i].data>maxn){
				maxn=dpp[i].data;
				u=i;
			}
		}
		int sum=0;
		for(i=dpp[u].sh;i<=dpp[u].eh;i++){
			for(j=dpp[u].sl;j<=dpp[u].el;j++){
				//if(j!=dpp[u].el) printf("%d ",buf[i][j]);
				//else printf("%d\n",buf[i][j]);
				sum+=buf[i][j];
			}
		}
		cout<<sum<<endl;
	}
	return 0;
}

问题 F: 放苹果

时间限制: 1 Sec   内存限制: 32 MB
提交: 15   解决: 13
[ 提交][ 状态][ TK题库][命题人: ]

题目描述

把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?(用K表示)5,1,1和1,5,1 是同一种分法。

输入

第一行是测试数据的数目t(0 <= t <= 20)。以下每行均包含二个整数M和N,以空格分开。1<=M,N<=10。

输出

对输入的每组数据M和N,用一行输出相应的K。

样例输入

2
6 3
7 2

样例输出

7
4

提示


解题分析:


         设f(m,n) 为m个苹果,n个盘子的放法数目,则先对n作讨论,


         当n>m:必定有n-m个盘子永远空着,去掉它们对摆放苹果方法数目不产生影响。即if(n>m) f(m,n) = f(m,m)  


         当n<=m:不同的放法可以分成两类:


         1、有至少一个盘子空着,即相当于f(m,n) = f(m,n-1);  


         2、所有盘子都有苹果,相当于可以从每个盘子中拿掉一个苹果,不影响不同放法的数目,即f(m,n) = f(m-n,n).


         而总的放苹果的放法数目等于两者的和,即 f(m,n) =f(m,n-1)+f(m-n,n) 


     递归出口条件说明:


         当n=1时,所有苹果都必须放在一个盘子里,所以返回1;


         当没有苹果可放时,定义为1种放法;


         递归的两条路,第一条n会逐渐减少,终会到达出口n==1; 


         第二条m会逐渐减少,因为n>m时,我们会return f(m,m) 所以终会到达出口m==0.
//(1)n>m:盘子数>苹果数,必定有n-m个盘子是空的,dp[m][n]=dp[m][m];
//(2)n<m:盘子数<苹果数,可分为两类:1)有空盘子:dp[m][n]=dp[m][n-1] 
//                                       2)没有空盘子:dp[m][n]=dp[m-n][n]
//(3)边界:n=1:1种,m=0:1种
#include<stdio.h>
#include<iostream>
using namespace std;
int main(){
	int t,i,j;
	cin>>t;
	while(t--){
		int m,n;
		cin>>m>>n;
		int dp[12][12];
		for(i=0;i<=m;i++){
			dp[i][0]=0;//没有盘子
			dp[i][1]=1;//1个盘子
		}
		for(i=0;i<=n;i++) dp[0][i]=1;//每个盘子都空
		for(i=1;i<=m;i++){
			for(j=1;j<=n;j++){
				if(i<j) dp[i][j]=dp[i][i];
				else dp[i][j]=dp[i][j-1]+dp[i-j][j];
			}
		}
		cout<<dp[m][n]<<endl;
	}
	return 0;
}

问题 G: 点菜问题

时间限制: 1 Sec   内存限制: 32 MB
提交: 21   解决: 18
[ 提交][ 状态][ TK题库][命题人: ]

题目描述

北大网络实验室经常有活动需要叫外买,但是每次叫外买的报销经费的总额最大为C元,有N种菜可以点,经过长时间的点菜,网络实验室对于每种菜i都有一个量化的评价分数(表示这个菜可口程度),为Vi,每种菜的价格为Pi, 问如何选择各种菜,使得在报销额度范围内能使点到的菜的总评价分数最大。
    注意:由于需要营养多样化,每种菜只能点一次。

输入

输入的第一行有两个整数C(1 <= C <= 1000)和N(1 <= N <= 100),C代表总共能够报销的额度,N>代表能点菜的数目。接下来的N行每行包括两个在1到100之间(包括1和100)的的整数,分别表示菜的>价格和菜的评价分数。

输出

输出只包括一行,这一行只包含一个整数,表示在报销额度范围内,所点的菜得到的最大评价分数。

样例输入

1 3
1 5
3 3
2 5
24 8
2 9
8 6
4 1
1 4
2 2
10 5
2 1
1 4

样例输出

5
30
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
int main(){
	int C,N,i,j;
	while(scanf("%d %d",&C,&N)!=EOF){
		int cost[110];
		int value[110];
		int dp[1010];
		for(i=1;i<=N;i++){
			cin>>cost[i]>>value[i];
		}
		for(i=0;i<=C;i++){
			dp[i]=0;
		}
		for(i=1;i<=N;i++){
			for(j=C;j>=cost[i];j--){
				dp[j]=max(dp[j],dp[j-cost[i]]+value[i]);
			}
		}
		int max=-1;
		for(i=0;i<=C;i++){
			if(dp[i]>max){
				max=dp[i];
			}
		}
		cout<<max<<endl;
	}
	return 0;
}

问题 H: 最大报销额

时间限制: 1 Sec   内存限制: 32 MB
提交: 42   解决: 2
[ 提交][ 状态][ TK题库][命题人: ]

题目描述

现有一笔经费可以报销一定额度的发票。允许报销的发票类型包括买图书(A类)、文具(B类)、差旅(C类),要求每张发票的总额不得超过1000元,每张发票上,单项物品的价值不得超过600元。现请你编写程序,在给出的一堆发票中找出可以报销的、不超过给定额度的最大报销额。

输入

测试输入包含若干测试用例。每个测试用例的第1行包含两个正数 Q 和 N,其中 Q(Q<=2000) 是给定的报销额度,N(N<=30)是发票张数。随后是 N 行输入,每行的格式为:
    m Type_1:price_1 Type_2:price_2 ... Type_m:price_m
其中正整数 m 是这张发票上所开物品的件数,Type_i 和 price_i 是第 i 项物品的种类和价值。物品种类用一个大写英文字母表示。当N为0时,全部输入结束,相应的结果不要输出。

输出

对每个测试用例输出1行,即可以报销的最大数额,精确到小数点后2位。

样例输入

300.00 3
2 A:33.50 B:150.00
1 C:850.00
3 A:159.99 A:350.00 X:10.00
1100.00 2
2 B:600.00 A:400.00
1 C:300.50
150.00 0

样例输出

183.50
1000.00
//先判断每张发票是否有效,留下有效发票,记录发票金额,01背包
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<sstream>
#include<map>
using namespace std;
int dp[20000010]={0};
bool deal(string str,int &sum){
	int len=str.find(" ");
	string strr=str.substr(0,str.find(" "));
	str.erase(0,len+1);
	int jianshu=atoi(strr.c_str());
	int i;
	for(i=0;i<jianshu;i++){
		if(str[0]=='A'||str[0]=='B'||str[0]=='C'){
			int lena=str.find(" ");
			string strra=str.substr(2,lena-2);//A:占两个
			//double a=atof(strra.c_str());
			int a=(int)(atof(strra.c_str())*100);//后面也要加()
			sum+=a;
			if(a>60000) return false;
			if(sum>100000) return false;
			str.erase(0,lena+1);
		}
		else return false;
	}
	return true;
}
int main(){
	double Q;
	int N,i,j;
	//double j;
	scanf("%lf %d",&Q,&N);
	getchar();
	while(N!=0){
		/*//发票有效:种类ABC,每件物品总额<=600,发票总价<=1000
		double cost[40]={0.0};
		//double dp[2010]={0.0};
		map<double,double> dp;*/
		int QQ=(int)(Q*100);//由于只有小数点后两位,可以将double转化为int,注意后面也要加()
		int cost[40]={0};
		dp[0]=0;
		int k=1;
		for(i=0;i<N;i++){
			string str;
			getline(cin,str);
			str=str+" ";
			int sum=0;
			if(deal(str,sum)){
				cost[k++]=sum;
			}
		}//统计有效发票金额
		for(i=1;i<k;i++){
			for(j=QQ;j>=cost[i];j--){
				//这里注意,j如果是double的话,就不能j-1,建个就不是1,按照题目,可以是j-0.01,但是测试内存超限
				dp[j]=max(dp[j],dp[j-cost[i]]+cost[i]);
			}
		}
		int max=-1;
		for(j=0;j<=QQ;j++){
			if(dp[j]>max){
				max=dp[j];
			}
		}
		//printf("%.2f\n",(double)max/100.00);
		int zheng=max/100;
		int xiao=max%100;
		printf("%d.%02d\n",zheng,xiao);
	    scanf("%lf %d",&Q,&N);
		getchar();
	}
	return 0;
}
/*200.00 3
2 A:23.50 B:100.00
1 C:650.00
3 A:59.99 A:120.00 X:10.00
1200.00 2
2 B:600.00 A:400.00
1 C:200.50
1200.50 3
2 B:600.00 A:400.00
1 C:200.50
1 A:100.00
100.00 0*/
//改用int后时间还是超限

//先判断每张发票是否有效,留下有效发票,记录发票金额,01背包
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<sstream>
#include<map>
#include<string.h>
using namespace std;
int dp[200010];
int main(){
	double Q;
	int N,i,j;
	scanf("%lf %d",&Q,&N);
	while(N!=0){
		//发票有效:种类ABC,每件物品总额<=600,发票总价<=1000
		int QQ=(int)(Q*100);//由于只有小数点后两位,可以将double转化为int,注意后面也要加()
		int cost[40]={0};
		memset(dp,0,sizeof(dp));
		int k=1;
		for(i=0;i<N;i++){
			int num;
			int sum=0;
			int flag=1;
			scanf("%d",&num);
			for(j=0;j<num;j++){
				char kind;
				double price;
				scanf(" %c:%lf",&kind,&price);//注意%c前面有一个空格
				int pricee=(int)(price*100);
				if(kind=='A'||kind=='B'||kind=='C'){
					if(pricee>60000) flag=0;
					else sum+=pricee;
					if(sum>100000) flag=0;
				}
				else flag=0;
			}
			if(flag==1) cost[k++]=sum;
		}//统计有效发票金额
		int sum[100];
		//sum[i]=cost[i]+cost[i+1]+...+cost[k-1];
		sum[k]=0;
		for(i=k-1;i>=0;i--){
			sum[i]=sum[i+1]+cost[i];
		}

		for(i=1;i<k;i++){
			int xiajie=max(QQ-sum[i+1],cost[i]);
			for(j=QQ;j>=xiajie;j--){
				dp[j]=max(dp[j],dp[j-cost[i]]+cost[i]);
			}
		}
		int max=-1;
		for(j=0;j<=QQ;j++){
			if(dp[j]>max){
				max=dp[j];
			}
		}
		int zheng=max/100;
		int xiao=max%100;
		printf("%d.%02d\n",zheng,xiao);
	    scanf("%lf %d",&Q,&N);
	}
	return 0;
}
/*200.00 3
2 A:23.50 B:100.00
1 C:650.00
3 A:59.99 A:120.00 X:10.00
1200.00 2
2 B:600.00 A:400.00
1 C:200.50
1200.50 3
2 B:600.00 A:400.00
1 C:200.50
1 A:100.00
100.00 0*/
//改用int后时间还是超限

猜你喜欢

转载自blog.csdn.net/li_jiaqian/article/details/79485203