一、数字金字塔,太经典了,可以自顶向下算,也可以自底向上算

二、子序列问题(线性DP)

(1)最长不下降子序列。很经典,注意分析题目,很多题目做点细节然后本质就是求最长不下降子序列

int n;
int a[201],c[201];
//本身,前驱 int b[201];
//得到的结果序列 //第一种做法,复杂度O(N^2) int main(){ cin>>n; for(int i=1;i<=n;i++){ cin>>a[i];b[i]=1;c[i]=0; } int l,k; for(int i=n-1;i>=1;i--){ l=0,k=0; for(int j=i+1;j<=n;j++){ if(a[j]>a[i]&&b[j]>l) { //选择大于它的且现在长度最大的 l=b[j]; k=j; } } if(l>0){ b[i]=l+1; c[i]=k; } } k=1; for(int i=1;i<=n;i++) if(b[i]>b[k]) k=i; cout<<"max="<<b[k]<<endl; //路径的输出 while(k!=0){ cout<<" "<<a[k]; k=c[k]; } return 0; }

第二种算法:复杂度O(Nlog2N)

两种操作,先与已经选择好的序列最后一位进行比较,如果大于最后一位,那么就直接放进去,len++;

如果比最后一位小,那么就在已经选择好的序列里面找到比它大的第一个数,然后替换,因为这样能够保障插入更多的数

//利用有序队列优化,f[i]=max(f[j]+1),j<i且a[j]<=a[i],而且f[j]要尽可能地大!!!
int n;
int a[maxn]; //本事 
int d[maxn]; //得到的结果序列
int pre[maxn]; //用来输出路径 

 
int main(){
	cin>>n;
	int len=1;
	for(int i=1;i<=n;i++) cin>>a[i];
	d[1]=a[1];pre[1]=1; 
	for(int i=2;i<=n;i++){
		if(d[len]<=a[i]) {
			d[++len]=a[i];pre[i]=len;
		}
		else{
			int j=upper_bound(d+1,d+1+len,a[i])-d; //返回第一个大于a[i]的坐标
			d[j]=a[i]; //否则就找到位置替换掉 
			pre[i]=j; 
		}
	}
	stack<int> st;
	for(int i=n,j=len;i>=1;i--){
		if(pre[i]==j){
			st.push(a[i]);
			--j;
		}
		if(j==0) break;
	}
	cout<<len<<endl;
	while(!st.empty()){
		cout<<st.top()<<" ";
		st.pop();
	}
return 0;
}

(2)最大连续子序列和

int a2[maxn],dp1[maxn]; //dp[i]表示以i为结尾的最大连续子序列和 
//最大连续子序列和
int findmaxsum(){
	int n;
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>a2[i];
	}
	dp1[0]=a2[0]; 
	for(int i=1;i<n;i++){
		dp1[i]=max(dp1[i-1]+a2[i],a2[i]);  //前一个为负数 
	}
	int maxx=-99999;
	for(int i=0;i<n;i++)  maxx=max(maxx,dp1[i]);
	return maxx;
} 
 

(3)最长回文子串

动态规划的做法:最简单O(N^2)、字符串hash+二分的算法(O(nlogn))

最优秀的Manacher算法O(N)

//最长回文子串
const int maxn=1001;
char a1[maxn];
int dp[maxn][maxn];//其实这是个类似于bool数组的作用,只是用来判断是不是回文串 
int findmaxhuiwen(){
	gets(a1);
	int ans=1;
	int len=strlen(a1);
	for(int i=0;i<len;i++){
		dp[i][i]=1;
		if(i+1<len){
			if(a1[i]==a1[i+1]) {
				dp[i][i+1]=1;
				ans=2;
			}
		}
	}//初始化 
	for(int l=3;l<=len;l++){ //以长度来循环 
		for(int i=0;i+l-1<len;i++){  //左边端点 
			int j=i+l-1; //右边端点 
			//里面不需要循环了,只有判断一次就够了
			if(a1[i]==a1[j]&&dp[i+1][j-1]) {
				dp[i][j]=1;
				ans=l; //l为长度 
			} 
		}
	} 
	return ans;
}

 涉及两个序列比较的:最长公共子序列、最长公共递增子序列

(4)最长公共子序列:简单

cin>>a>>b;
	int ma=strlen(a);
	int mb=strlen(b);
	for(int i=1;i<=ma;i++){
		for(int j=1;j<=mb;j++){
			if(a[i-1]==b[j-1]) f[i][j]=f[i-1][j-1]+1;//注意这里是a[i-1]==b[i-1] 
			else f[i][j]=max(f[i-1][j],f[i][j-1]);   //配合前面i=1,j=1 
		}
	} 
	cout<<f[ma][mb]<<endl;

(5)最长公共上升子序列

int a[501],b[501];
int t[501][501]={0}; //用来记录的 
int s[501];      //临时存储 
int n,m;
int main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	cin>>m;
	for(int i=1;i<=m;i++) cin>>b[i];
//	a[0]=b[0]=-999999;//预处理边界值
	//以b为大循环,遍历a与之比较 
	for(int i=1;i<=m;i++){
		memset(s,0,sizeof(s));//初始化s
		for(int j=1;j<=n;j++){
			if(b[i]>a[j]&&s[0]<t[j][0]){//符合局部上升且之前存储过公共上升子序列 
				memcpy(s,t[j],sizeof(t[j])); //就保存到s中(调出之前存储的公共上升子序列) 
			}
			
			if(b[i]==a[j]){
				memcpy(t[j],s,sizeof(s));//将s复制给t,存储当前情况下子序列
				t[j][++t[j][0]]=a[j];//接上子序列并计算长度+1 
			}
		}
	}
		int ans=0;//找出最大长度 
		for(int i=1;i<=n;i++) if(ans<t[i][0]) ans=t[i][0];
		cout<<ans<<endl;
		for(int i=1;i<=n;i++) {
			if(t[i][0]==ans){
				for(int j=1;j<=ans;j++) cout<<t[i][j]<<" ";
				break;
			}
		} 
	 
return 0;
}

https://blog.csdn.net/lq1990717/article/details/124222748

这个讲的很好

#include<bits/stdc++.h>
using namespace std;
#define N 505
int dp[N];
int x[N],y[N];
vector<int> seq[N];
//https://blog.csdn.net/lq1990717/article/details/124222748
int main(){
	int n,m;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>x[i];
	cin>>m;
	for(int i=1;i<=m;i++) cin>>y[i];
	for(int i=1;i<=n;i++){
		int mj=0; //下标优化
		for(int j=1;j<=m;j++){
			if(y[j]<x[i]&&dp[j]>dp[mj]) mj=j;
			if(x[i]==y[j]){
				dp[j]=dp[mj]+1;
				seq[j]=seq[mj];
				seq[j].push_back(x[i]);
			}
		} 
	}
	int mi=1;
	for(int i=1;i<=m;i++){
		if(dp[i]>dp[mi]) mi=i;
	}
	cout<<dp[mi]<<endl;
	for(int i=0;i<seq[mi].size();i++) cout<<seq[mi][i]<<" ";
	return 0;
} 

 

三、拦截导弹,也太经典了

一个最多能拦多少,就是最长不下降子序列

cin>>n;
	for(int i=1;i<=n;i++) {cin>>a[i];b[i]=1;
	}
	int l=0,m=0,nn=1,k;
	h[1]=a[1];
	for(int i=n-1;i>=1;i--){
		l=0;
		for(int j=i+1;j<=n;j++){
			if(a[i]>a[j]&&b[j]>l) l=b[j];
		}
		if(l>0) {
			b[i]=l+1;
		}
		//if(b[i]>m) m=b[i]; //最多能拦多少
	}
	for(int i=1;i<=n;i++) m=max(m,b[i]);
	for(int i=2;i<=n;i++){
		k=0;
		for(int j=1;j<=nn;j++){
			if(h[j]>=a[i]) {//能拦 
				if(k==0) k=j;
				else if(h[j]<h[k]) k=j; //还有更低的导弹高度 
			}
		}
		if(k==0) {nn++;
		h[nn]=a[i];}//增加导弹数量 
		else h[k]=a[i]; //否则更新导弹高度 
		
	}
	cout<<m<<" "<<nn<<endl;

四、机器分配,这个输出比较特别,状态转移方程明白意思

int n,m;
int f[11][16];
int a[11][16];
//记住这个输出!!!很特别 
int show(int x,int y){  //x位公司数,y位剩下的台数 
	int k;
	if(x==0 ) return 0;
	for(k=0;k<=y;k++){
		if(f[n][m]==f[x-1][k]+a[x][y-k]) {  //前面分了k台,即剩下k台全部是前面的 
			f[n][m]-=a[x][y-k];
			show(x-1,k);
			cout<<x<<" "<<y-k<<endl; //顺序 
			break;
		}
	}
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cin>>a[i][j];
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			for(int k=0;k<=j;k++) f[i][j]=max(f[i][j],f[i-1][k]+a[i][j-k]);  //状态转移方程含义 
			//前i个公司分配j台能够得到的最大值    前i个分k台,第i个分配j-k台 
		}//k为中间控制变量 
	}
	cout<<f[n][m]<<endl;
	show(n,m);
return 0;
}

五、背包问题

  • 01背包:每个东西只有一份
cin>>m>>n;
	for(int i=1;i<=n;i++) cin>>w[i]>>c[i];
	for(int i=1;i<=n;i++){
		for(int j=m;j>=w[i];j--){   //逆序 
			f[j]=max(f[j-w[i]]+c[i],f[j]); //这里是J 
		}
	}
	cout<<f[m]<<endl;
  • 完全背包:每个东西可以取无限份
cin>>m>>n;
	for(int i=1;i<=n;i++) cin>>w[i]>>c[i];
	for(int i=1;i<=n;i++){//顺序 
		for(int j=w[i];j<=m;j++) if(f[j]<f[j-w[i]]+c[i]) f[j]=f[j-w[i]]+c[i]; 
	}
	cout<<"max="<<f[m]<<endl;
  • 混合背包:有的物品有多个有的物品只有1个,分别进行处理,逆序or正序,注意选择多个的时候循环次序,物品数量在外层,背包重量在内层
cin>>m>>n;
	for(int i=1;i<=n;i++) cin>>w[i]>>c[i]>>s[i];
	for(int i=1;i<=n;i++){
		if(s[i]==0){ //完全背包,顺序 
			for(int j=w[i];j<=m;j++){
				f[j]=max(f[j],f[j-w[i]]+c[i]);
			}
		}
		else {//01背包和多重背包,逆序 
			for(int j=1;j<=s[i];j++)  //注意顺序,先是物品个数,再是剩余重量 
			for(int k=m;k>=w[i];k--)
			f[k]=max(f[k],f[k-w[i]]+c[i]); //这里没有j噢 
		}
	}
	cout<<f[m]<<endl;
  • 多重背包,可以通过二进制处理降低复杂度,原理是每个数都可以表示位2进制相加的形式,所以这样就把它处理为了01背包问题 
  • 	int x,y,n1=0,s,t;
    	for(int i=1;i<=n;i++){
    		cin>>x>>y>>s;
    		t=1;
    		while(s>=t){
    			w[++n1]=t*x;  //重量 
    			c[n1]=t*y;     //价值 
    			s-=t;
    			t*=2;
    		}
    		w[++n1]=s*x;
    		c[n1]=s*y;
    	} 
    	//接下来就是01背包问题 
    	for(int i=1;i<=n1;i++){  //注意数量 
    		for(int j=m;j>=w[i];j--){
    			f[j]=max(f[j],f[j-w[i]]+c[i]);
    		}
    	}
    	cout<<f[m]<<endl;
  • 二维背包问题:eg.潜水员
cin>>m>>n>>k;
	memset(f,127,sizeof(f));//赋值为一个很大的数,因为是要求最小的 
	f[0][0] =0;             //f[0][0]要初始化 
	for(int i=1;i<=k;i++) cin>>mm[i]>>nn[i]>>w[i];
	for(int i=1;i<=k;i++){
		for(int j=m;j>=0;j--){//都是01背包 费用1 
			for(int k=n;k>=0;k--){       //费用2 
				int t1=j+mm[i];
				int t2=k+nn[i];  //注意写法 ,不能在外面直接判断 
				if(t1>m) t1=m;
				if(t2>n) t2=n;
				if(f[t1][t2]>f[j][k]+w[i]) f[t1][t2]=f[j][k]+w[i]; 
			}
		}
	}
	cout<<f[m][n]<<endl;
  • 分组背包,背包分组,注意循环的顺序,最外层是组数,中间层是剩余的体积,最里层是这个组里面的物品序号
int p=0;
	cin>>v>>n>>k;
	for(int i=1;i<=n;i++){
		cin>>w[i]>>c[i]>>p;
		g[p][++g[p][0]]=i;     //这种写法,合并了两个功能 
	}
	for(int i=1;i<=k;i++){   //分的组数 
		for(int j=v;j>=0;j--){  //第二层是体积 
			for(int k=1;k<=g[i][0];k++){ //这个组的物品序号 
				if(j>=w[g[i][k]]){   //注意是>=,不然结果不正确 
					int temp=g[i][k];
					if(f[j]<f[j-w[temp]]+c[temp]) f[j]=f[j-w[temp]]+c[temp];
				}
			}
		}
	}
	cout<<f[v]<<endl;

六、货币系统,两种写法,与背包问题不同的是,这里是求的方案总数,所以直接加就好了,不用比较

cin>>n>>m;
	//这里是逆序的写法: 01背包 
	for(int i=1;i<=n;i++) cin>>a[i];
	f[0]=1;  //初始化 
	for(int i=1;i<=n;i++){
		for(int j=m;j>=a[i];j--){
			for(int k=1;k<=j/a[i];k++){  //注意这里有个数隐形限制 ,而且注意除的是J 
				f[j]=f[j]+f[j-k*a[i]];
			}
		}
	}
	cout<<f[m]<<endl;
	//下面是顺序的写法:完全背包 
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	f[0]=1;
	for(int i=1;i<=n;i++){
		for(int j=a[i];j<=m;j++){
			f[j]+=f[j-a[i]]; 
		}
	}
	cout<<f[m]; 

七、数字组合,与货币系统很像,但是这个是01背包,那个是完全背包

//这道题和货币系统那道题很相似,都是求方案总数
//但是这道题是01背包
//货币系统是完全背包 
int main(){
	cin>>n>>t;
	for(int i=1;i<=n;i++) cin>>a[i];
	f[0]=1;
	for(int i=1;i<=n;i++){
		for(int j=t;j>=a[i];j--){
			f[j]+=f[j-a[i]];
		}
	}
	cout<<f[t]<<endl;

八、开餐馆

其实思路蛮简单,直接遍历每一个点,然后从开头到结尾,每个距离它有k的餐馆都加进去,然后选择最大的就可以了

int t;
    cin>>t;
    while(t--)
    {
        int n,k;
        cin>>n>>k;
        for(int i=1;i<=n;i++)
            cin>>w[i];
        for(int i=1;i<=n;i++)
        {
            cin>>c[i];
            f[i]=c[i];
        }
 
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                if(w[i]-w[j]>k)
                    f[i]=max(f[i],f[j]+c[i]);
 
        int maxx=-INF;
        for(int i=1;i<=n;i++)
            maxx=max(f[i],maxx);
        cout<<maxx<<endl;
    }

九、合并石子,注意这个合并石子是只能合并相邻的,如果随意的话就是堆(小根堆)

cin>>n;
	for(int i=1;i<=n;i++) {
		cin>>a[i];
		s[i]=s[i-1]+a[i];
	}
	memset(f,127/3,sizeof(f));
	for(int i=1;i<=n;i++) f[i][i]=0;  //赋一个极大的值但是相加不会溢出 
	for(int i=n-1;i>=1;i--){
		for(int j=i+1;j<=n;j++){
			for(int k=i;k<=j-1;k++){
				if(f[i][j]>f[i][k]+f[k+1][j]+s[j]-s[i-1]) //把i--k和k+1--j这两堆合并起来,原本的得分加上现在的新增的得分 
				f[i][j]=f[i][k]+f[k+1][j]+s[j]-s[i-1];
			}
		}
	}
	cout<<f[1][n]<<endl;

十、乘积最大,注意细节,以及循环的下标和循环的顺序

int n,m;
long long s; //把字符串转化为数组存储。方便转化 
long long f[11][7],a[11][11]; //注意这个也要用Longlong存储 
int main(){
	cin>>n>>m;
	cin>>s;
	for(int i=n;i>=1;i--){
		a[i][i]=s%10;
		s/=10;
	}
	for(int i=2;i<=n;i++){
		for(int j=i-1;j>=1;j--){
			a[j][i]=a[j][i-1]*10+a[i][i];
		}
	} 
	//以上为处理 a数组的含义
	for(int i=1;i<=n;i++) f[i][0]=a[1][i];  //前i位没有乘号,就是本身 
	//预处理:不加乘号的情况 
	for(int i=1;i<=m;i++)  //这里的循环是针对乘号个数的 
	for(int j=i+1;j<=n;j++)  //可以插入的 
	for(int k=i;k<j;k++) {  //寻找合适的插入位置 ,不能少于乘号数,也不能大于j 
	f[j][i]=max(f[j][i],f[k][i-1]*a[k+1][j]);
	}
	cout<<f[n][m]<<endl; 
return 0;
}

十一、编辑距离。注意这个与公共子序列的区别,那个是上面+1

设A和B是两个字符串。我们要用最少的字符操作次数,将字符串A转换为字符串B。这里所说的字符操作共有三种:

1、删除一个字符;

2、插入一个字符;

3、将一个字符改为另一个字符。

对任意的两个字符串A和B,计算出将字符串A变换为字符串B所用的最少字符操作次数。

cin>>a>>b;
	int la=strlen(a);
	int lb=strlen(b);
	for(int i=1;i<=la;i++) f[i][0]=i;
	for(int i=1;i<=lb;i++) f[0][i]=i;
	for(int i=1;i<=la;i++)  //开头写错了!应该是i=1 
	for(int j=1;j<=lb;j++){
		if(a[i-1]==b[j-1]) f[i][j]=f[i-1][j-1];
		else f[i][j]=min(min(f[i-1][j],f[i][j-1]),f[i-1][j-1])+1;
	}
	cout<<f[la][lb]<<endl;

十二、方格取数,进行四重循环,弄清如果是同一个地方的话,就只取一次

cin>>n;
	int x,y,z;
	while(cin>>x>>y>>z,x+y+z){
		map[x][y]=z;
	}
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	for(int h=1;h<=n;h++)
	for(int k=1;k<=n;k++){
		int temp1=max(f[i-1][j][h-1][k],f[i-1][j][h][k-1]); //下下  下右 
		int temp2=max(f[i][j-1][h-1][k],f[i][j-1][h][k-1]); //右下  右右 
		if(i!=h&&j!=k) f[i][j][h][k]=max(temp1,temp2)+map[i][j]+map[h][k];
		else f[i][j][h][k]=max(temp1,temp2)+map[i][j];
	} 
	cout<<f[n][n][n][n]<<endl;

十三、复制书稿

现在要把m本有顺序的书分给k个人复制(抄写),每一个人的抄写速度都一样,一本书不允许给两个(或以上)的人抄写,分给每一个人的书,必须是连续的,比如不能把第一、第三和第四本书给同一个人抄写。

现在请你设计一种方案,使得复制时间最短。复制时间为抄写页数最多的人用去的时间。

【输入】

第一行两个整数m,k;(k≤m≤500)

第二行m个整数,第i个整数表示第i本书的页数。

【输出】

共k行,每行两个整数,第i行表示第i个人抄写的书的起始编号和终止编号。k行的起始编号应该从小到大排列,如果有多解,则尽可能让前面的人少抄写。

 这个输出的函数很special

int m,k;
int a[501],f[501][501],d[501];//书的页数,结果,前几本书的总页数 
void print(int x,int y){//分配抄书策略 ,贪心,逆序 
	if(y==0) return ;
	if(y==1) cout<<"1 "<<x<<endl;  //x是书,是可变的,y是人,只有循环没有变化 
	int t=x,xx=a[x];
	while(xx+a[t-1]<=f[k][m]){
		xx+=a[t-1];
		t--;
	}
	print(t-1,y-1);
	cout<<t<<" "<<x<<endl;  //t是变化的i,而i是进来时的变量 
	return;
}
int main(){
	cin>>m>>k;
	memset(f,100000,sizeof(f));
	for(int i=1;i<=m;i++){
			cin>>a[i];
			d[i]=d[i-1]+a[i];
			f[1][i]=d[i];  //前几个个人抄几本书 
	}
	for(int i=2;i<=k;i++){  //人:阶段
	for(int j=1;j<=m;j++){  //书:状态 
		for(int k=1;k<=j-1;k++){
			if(max(f[i-1][k],d[j]-d[k])<f[i][j])
			f[i][j]=max(f[i-1][k],d[j]-d[k]);//前i-1个人抄k本书,第i个人抄d[j]-d[k]本书 取max是因为求的是时间 
		}
	} 
		
	}
print(m,k); //输出 
	
return 0;
}

十四、橱窗布置

这道题要注意很多细节,比如路径的保存,以及循环中下标的设置

//橱窗布置
int a[101][101],b[101][101];//分别是美学值,前i朵花放在前j个花瓶 
int c[101][101],d[101];//存储路径 
int f,v;
int main(){
	cin>>f>>v;
	for(int i=1;i<=f;i++)
	for(int j=1;j<=v;j++){
		cin>>a[i][j];
	}
	memset(b,128,sizeof(b));//将数组b初始化为一个很小的数
	for(int i=1;i<=v-f+1;i++) b[1][i]=a[1][i];//第一束花放在第i个瓶子里=第一束花放在第一个瓶子里(含义是一样的)
	                               //注意上限是v-f+1 
	for(int i=2;i<=f;i++){//枚举花 
		for(int j=i;j<=v-f+i;j++){//枚举花瓶 (注意上限是v-f+i)
			for(int k=i-1;k<=j-1;k++){//枚举中间位置 
				if(b[i][j]<b[i-1][k]+a[i][j]){
					b[i][j]=b[i-1][k]+a[i][j];//如果前i-1束花放在前k个花瓶里再加上i放j上的美学值要大于前i束花放在前j个花瓶 
					c[i][j]=k;//存储路径 
				}
			}
		}
	} 
	int k;//存放最后一束花的位置 
	int maxx=-999999;
	for(int i=f;i<=v;i++){
		if(maxx<b[f][i]){
			maxx=b[f][i];
			k=i;//存放最后一束花的位置 
		}
	} 
	cout<<maxx<<endl;
	//!!!!!!! 
	//下面是输出
	for(int i=1;i<=f;i++){
		d[i]=k;
		k=c[f-i+1][k];//d[i]是在逆序存储 
	} 
	for(int i=f;i>=2;i--){
		cout<<d[i]<<" ";//为了不输出多余的空格 
	}
	cout<<d[1]<<endl;
return 0;
}

十五、滑雪

记忆化搜索,加上动态规划,注意搜索过程的写法

int n,c,map[101][101];
int f[101][101];//存储结果的 
int dis[4][2]={{0,1},{0,-1},{1,0},{-1,0}};
int seach(int x,int y){
	if(f[x][y]) return f[x][y]; //记忆化搜索 
	int t=1,temp;
	for(int i=0;i<4;i++){
		int xx=x+dis[i][0];
		int yy=y+dis[i][1];
		if(xx>=1&&xx<=n&&yy>=1&&yy<=c&&map[xx][yy]>map[x][y]){
			temp=seach(xx,yy)+1;
			if(temp>t) t=temp;//这里是存储最优 
		}
	}
	f[x][y]=t;  //如果没有上面的的过程,就会直接返回1,然后在不断的搜索过程中会变大,也就是返回的t 
	return t;
}

int main(){
	cin>>n>>c;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=c;j++){
		cin>>map[i][j];
	}
	int t,ans=-9999;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=c;j++){
		t=seach(i,j);
		f[i][j]=t;//记得保存结果 
		if(t>ans) ans=t;
	}
	cout<<ans<<endl;
return 0;
}

十六、糖果

由于在维护世界和平的事务中做出巨大贡献,Dzx被赠予糖果公司2010年5月23日当天无限量糖果免费优惠券。在这一天,Dzx可以从糖果公司的N件产品中任意选择若干件带回家享用。糖果公司的N件产品每件都包含数量不同的糖果。Dzx希望他选择的产品包含的糖果总数是K的整数倍,这样他才能平均地将糖果分给帮助他维护世界和平的伙伴们。当然,在满足这一条件的基础上,糖果总数越多越好。Dzx最多能带走多少糖果呢?

注意:Dzx只能将糖果公司的产品整件带走。

【输入】

第一行包含两个整数N(1≤N≤100)和K(1≤K≤100)。

以下N行每行1个整数,表示糖果公司该件产品中包含的糖果数目,不超过1000000。

【输出】

符合要求的最多能达到的糖果总数,如果不能达到K的倍数这一要求,输出0。

int n,k;
int a[101];
int f[101][101];//题目说了k的大小,就要考虑把k当成状态
//f[i][j]的意思是前i个物品里面余数为j的最多糖果数 

int main(){
	cin>>n>>k;
	for(int i=1;i<=n;i++) cin>>a[i];
	memset(f,-128,sizeof(f));//赋值为负无穷
	f[0][0]=0;
	f[1][0]=0;
	f[1][a[1]%k]=a[1];//取第一件 
	for(int i=2;i<=n;i++){
		for(int j=0;j<k;j++){//把余数当作状态
		f[i][j]=max(f[i-1][j],f[i-1][(k+j-a[i]%k)%k]+a[i]);//取a[i]和不取a[i] 
			                        //注意这里的写法(j-a[i]%k+k)%k 因为有可能是负数嘛 
		}
	}
	if(f[n][0]!=0) cout<<f[n][0]<<endl;
	else cout<<"0"<<endl; 
	 
return 0;
}

十七、鸡蛋的硬度

最近XX公司举办了一个奇怪的比赛:鸡蛋硬度之王争霸赛。参赛者是来自世界各地的母鸡,比赛的内容是看谁下的蛋最硬,更奇怪的是XX公司并不使用什么精密仪器来测量蛋的硬度,他们采用了一种最老土的办法--从高度扔鸡蛋--来测试鸡蛋的硬度,如果一次母鸡下的蛋从高楼的第a层摔下来没摔破,但是从a+1层摔下来时摔破了,那么就说这只母鸡的鸡蛋的硬度是a。你当然可以找出各种理由说明这种方法不科学,比如同一只母鸡下的蛋硬度可能不一样等等,但是这不影响XX公司的争霸赛,因为他们只是为了吸引大家的眼球,一个个鸡蛋从100 层的高楼上掉下来的时候,这情景还是能吸引很多人驻足观看的,当然,XX公司也绝不会忘记在高楼上挂一条幅,写上“XX公司”的字样--这比赛不过是XX 公司的一个另类广告而已。

勤于思考的小A总是能从一件事情中发现一个数学问题,这件事也不例外。“假如有很多同样硬度的鸡蛋,那么我可以用二分的办法用最少的次数测出鸡蛋的硬度”,小A对自己的这个结论感到很满意,不过很快麻烦来了,“但是,假如我的鸡蛋不够用呢,比如我只有1个鸡蛋,那么我就不得不从第1层楼开始一层一层的扔,最坏情况下我要扔100次。如果有2个鸡蛋,那么就从2层楼开始的地方扔……等等,不对,好像应该从1/3的地方开始扔才对,嗯,好像也不一定啊……3个鸡蛋怎么办,4个,5个,更多呢……”,和往常一样,小A又陷入了一个思维僵局,与其说他是勤于思考,不如说他是喜欢自找麻烦。

好吧,既然麻烦来了,就得有人去解决,小A的麻烦就靠你来解决了:)

【输入】

输入包括多组数据,每组数据一行,包含两个正整数n和m(1≤n≤100,1≤m≤10),其中n表示楼的高度,m表示你现在拥有的鸡蛋个数,这些鸡蛋硬度相同(即它们从同样高的地方掉下来要么都摔碎要么都不碎),并且小于等于n。你可以假定硬度为x的鸡蛋从高度小于等于x的地方摔无论如何都不会碎(没摔碎的鸡蛋可以继续使用),而只要从比x高的地方扔必然会碎。

对每组输入数据,你可以假定鸡蛋的硬度在0至n之间,即在n+1层扔鸡蛋一定会碎。

【输出】

对于每一组输入,输出一个整数,表示使用最优策略在最坏情况下所需要的扔鸡蛋次数。

 

这道题当时做的时候完全无思路,后来看了题解(也是有点绕)之后觉得,这就是个逻辑题and数学题

int h,n;
/*
这个题目有很多地方都有分析,我也看了好几个解答,但都说得不太明白(也许是我笨),我结合听的讲座和各家分析,加上自己的试验作以下分析。

这时的策略大体如下:第一,最简单的和种情况就是只有一只鸡蛋,那只能从一楼开始一层一层往上走,如果第一楼就破了,那硬度就是0,否则总能找到破
与不破的两层。那意味着硬度为多少就得比硬度数多扔一次就好。如果题目给的楼层是100,那最坏的可能就是硬度为99,必须扔100次。(硬度不能为100,
题目说了硬度小于n,也意味着从顶楼扔下,鸡蛋一定会碎,但这一次必须要扔)。第二,如果有两枚鸡蛋呢?那第一次就不用在第一楼扔,那在多少楼扔合
适?答案是第14楼。那要是破了呢?说明硬度最大13,同时我们手里的鸡蛋也只有一枚了,那办法就只有一个了:从第一楼开始往上,根据刚才的经验,最
多再扔13次就好(1-13楼全试,只有一枚鸡蛋,不能冒险哟)。那要是没破呢?又在哪扔呢?第27楼。要是破了,硬度在14-26之间,最多再试12次就可以了,
加上之前14楼和27楼两次,共14次。要是没破,下一次选在39楼。同样要是破了,就从28开始往上走,最多走到第38楼,共14次,要是没破,就选50楼。。。。
,以后每多一次间距就少一,均能保证14次能测出硬度。第三,为什么是14楼开始?10楼不行么?如果选10楼,要是破了,当然比刚才更快,那要是没破呢?
下一次选多少楼?20?然后30、40、50、60、70、80、90?到了60己经扔了6次了,要是破了,最坏的情况是要再扔9次,在是没破又如何继续?明显不是最佳
策略。那为什么是14次?按照刚才的策略1+2+3+...+14=105超过100了,说明14次一定能测出结果。第四,要是鸡蛋有更多个呢?比如有三个,第一次可以考虑
对分:第50楼,要是不破,再二分,75楼.....,要是破了,就相当于两个鸡蛋测50楼,1+2+3+...+10=55,再有10次就可以搞定了。如果有四个鸡蛋呢?两次
就把范围缩小到25楼了,1+2+3+...+7=28,共需9次。那要是鸡蛋无限呢,最多也是全二分,2^7=128,这个就是最效率的了,换句话说,对100层楼而言,多于
7个鸡蛋也没用。第五,如果有m个鸡蛋n层楼呢?我们用f[n][m]记最少的次数。那可以枚举从第k层楼扔下,有两种情况:(1)鸡蛋破了,那硬度在1-k-1之间
,我们得用余下的m-1个鸡蛋测试k-1层楼,这时f[n][m]=f[k-1][m-1],(2)鸡蛋没破。那再测余下的k~n层楼就好,这时f[n][m]=f[n-k][m],题目要求是最坏
的情况,故需要取这两种情况的最大值。故f[n][m]=max(f[k-1][m-1],f[n-k][m])+1。对所有的k的取值得到若干个f[n][m],这个我们可以选择k值,故把这些
值再取最小值。f[n][m]=min(f[n][m],max(f[k-1][m-1],f[n-k][m])+1),这就是帅气的状态转移方程了
*/
int f[101][11];
int main(){
	while(cin>>h>>n){
		memset(f,0,sizeof(f));
		for(int i=1;i<=h;i++){
			for(int j=1;j<=n;j++) f[i][j]=i;   //记住这个初值 
		} 
	
	for(int i=1;i<=h;i++){
		for(int j=2;j<=n;j++){
			for(int k=1;k<=i;k++){
				f[i][j]=min(f[i][j],max(f[k-1][j-1],f[i-k][j])+1);
				//一个是破了,剩余需要检验的是k-1层,
				//另一个是没破,剩余需要检验i-k层
				//最坏情况:所以用max 
			}
		}
	}
	cout<<f[h][n]<<endl; 
	} 
return 0;
}

十八、大盗A福

这道题想通了就是很简单的嘛,就是在现在的情况下,要么九偷上上个要么就偷上上上个

int t,n;
int a[100001];
int f[100001];  //偷前i个能够得到最大值 
int main(){
	cin>>t;
	while(t--){
		cin>>n;
		memset(a,0,sizeof(a));
		memset(f,0,sizeof(f));
		for(int i=1;i<=n;i++) cin>>a[i];
		f[0]=0;
		f[1]=a[1];
		f[2]=a[2];
		int maxx=a[1];
		if(n>1&&a[2]>a[1]) maxx=a[2]; //注意条件n>1 
		//f[i]表示前i个店铺最多能得到的 
		for(int i=3;i<=n;i++){
			f[i]=max(f[i-3],f[i-2])+a[i]; //状态转移方程:要么偷上上个,要么偷上上上个 
			if(maxx<f[i]) maxx=f[i];
		}
		cout<<maxx<<endl;
	}
return 0;
}

十九、股票买卖

最近越来越多的人都投身股市,阿福也有点心动了。谨记着“股市有风险,入市需谨慎”,阿福决定先来研究一下简化版的股票买卖问题。

假设阿福已经准确预测出了某只股票在未来N天的价格,他希望买卖两次,使得获得的利润最高。为了计算简单起见,利润的计算方式为卖出的价格减去买入的价格。

同一天可以进行多次买卖。但是在第一次买入之后,必须要先卖出,然后才可以第二次买入。

现在,阿福想知道他最多可以获得多少利润。

这道题不难(优点序列的意思),两边循环查找,分别用两个数组来表示就可以了,最后maxx=max(maxx,q[i]+f[i]);

int sum=0,n,t;
int a[100001];
int f[100001];//顺序的
int q[100001];//逆序的 
int main(){
	cin>>t;
	while(t--){
		scanf("%d",&n);
		int minn=999999,maxx=-999999;
		memset(a,0,sizeof(a));
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
		}
		//这里用cin超时了!!!数据大时还是用scanf 
		//我过于注重顺序了
		//导致思维局限‘
		f[0]=0;
		q[n+1]=0;//初始化 
			for(int i=1;i<=n;i++){
				minn=min(a[i],minn);
				f[i]=max(f[i-1],a[i]-minn);//要么这天不卖,要么这天卖 
			}
			for(int i=n;i>=1;i--){
				maxx=max(maxx,a[i]);
				q[i]=max(q[i+1],maxx-a[i]);//要么这天不买,要么这天买入 
			} 
			maxx=-9999;
			for(int i=1;i<=n;i++){
				maxx=max(maxx,q[i]+f[i]);
			}
			printf("%d\n",maxx);
	}
return 0;
}

二十、鸣人的影分身

在火影忍者的世界里,令敌人捉摸不透是非常关键的。我们的主角漩涡鸣人所拥有的一个招数——多重影分身之术——就是一个很好的例子。

影分身是由鸣人身体的查克拉能量制造的,使用的查克拉越多,制造出的影分身越强。

针对不同的作战情况,鸣人可以选择制造出各种强度的影分身,有的用来佯攻,有的用来发起致命一击。

那么问题来了,假设鸣人的查克拉能量为M,他影分身的个数最多为N,那么制造影分身时有多少种(用K表示)不同的分配方法?(影分身可以被分配到0点查克拉能量)

int t,n,m;
int f[11][11];
int main(){
	cin>>t;
	for(int i=0;i<=10;i++){//能量 
		for(int j=0;j<=10;j++){//身体数 
			if(j==1||i==0||i==1) f[i][j]=1;
			//一个身体、0点或1点能量-----都只有一种方案 
			else if(j>i) f[i][j]=f[i][i];
			else f[i][j]=f[i-j][j]+f[i][j-1];  //第一种情况:每个分身都有1点能量加上多余的能产生的
			    //第二种情况:减少一个身体能够产生的 
			//分为无0和有0 !!!!!记住 
		}
	} 
	while(t--){
		cin>>n>>m;
		cout<<f[n][m]<<endl;
	} 
return 0;
}

二十一、数的划分

将整数n分成k份,且每份不能为空,任意两份不能相同(不考虑顺序)。

例如:n=7,k=3,下面三种分法被认为是相同的。

1,1,5; 1,5,1; 5,1,1;

问有多少种不同的分法。 输出一个整数,即不同的分法。

int f[8][201];
int main()
{
int m,n;
cin>>m>>n; //qiushu  heshu
f[0][0]=1;
for(int i=1;i<=n;i++){
	for(int j=i;j<=m;j++){
		f[i][j]=f[i-1][j-1]+f[i][j-i];  //要么划分给第i个数1,要么用剩下的j-i去分
	}
}
cout<<f[n][m]<<endl;
    return 0;
}

  

 posted on 2020-02-06 22:06  shirlybabyyy  阅读(489)  评论(0编辑  收藏  举报