• 动态规划一本通题目


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

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

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

    合唱队形:从两边求最长不下降子序列,然后遍历每一个点,分析它的两边的情况,选择最大的

    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;
    }
    • 最长公共子序列,最简单的了
    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;
    
    • 最长公共上升子序列
    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;
    }
    
    • 最长回文子串

    动态规划的做法:最简单O(N^2)

    字符串hash+二分的算法(O(nlogn))

    最优秀的Manacher算法

    //字符串hash+二分
    //写写思路:首先可以先到把字符串反转,计算两个字符串的hash值,然后进行比较,看最大的半径(以分界点为中心)在哪里,但是如果单纯枚举半径
    //就会超时,所以二分回文半径
    //但是有需要区分:回文串长度是奇数还是偶数(因为区间不同,所以分别计算:回文长度为偶数和回文长度为奇数的情况,计算最大的回文半径
    //《算法笔记》第45页 
    long long pw[maxn],h1[maxn],h2[maxn];
    void inti(int len){
        pw[0]=1;
        for(int i=1;i<=len;i++) pw[i]=(P*pw[i-1])%MOD; //计算每一位进制数 
    } 
    void gethash(string &s,long long pe[]){               //计算字符串的hash数组
        pe[0]=s[0]-'a'; 
        for(int i=1;i<s.length();i++){
            pe[i]=(pe[i-1]*P+s[i]-'a')%MOD;
        }
    }
    long long jssubstrhash(long long h[],int i,int j){    //计算子串的hash值 
        if(i==0) return h[j]; //如果以0开头,就直接返回(已经计算过了)
        //公式:h[i..j]=((h[j]-h[i-1]*p^(j-i+1))%MOD+MOD)%MOD 
        else return  ((h[j]-h[i-1]*pw[j-i+1])%MOD+MOD)%MOD;
    }
    //回文半径上下限为l,r,分界点为i,字符串串长为len,判断是不是整数iseven
    int getsearch(int l,int r,int len,int i,int iseven){
        while(l<r){
            int mid=(l+r)/2;
            int h1l=i-mid+iseven,h1r=i;
            int h2l=len-1-(i+mid),h2r=len-1-(i+iseven);
            int hash1=jssubstrhash(h1,h1l,h1r);
            int hash2=jssubstrhash(h2,h2l,h2r);
            if(hash1!=hash2) r=mid; //不匹配:回文半径太大了,减小一点
            else l=mid+1; //相等的话说明还可能可以扩充 
        }
        return l-1; //返回最大回文半径 
    } 
    string str;
    void findmaxbanjing(){
        getline(cin,str);
        inti(str.length()); 
            gethash(str,h1); //计算原来的串的hash
        reverse(str.begin(),str.end()); //反转这个串
        gethash(str,h2);
        //区分奇数回文长度和偶数回文长度
        int ans=0;
        for(int i=0;i<str.length();i++){
            int maxlen=min(i,(int)str.length()-1-i)+1; //最大的回文半径(上限)--左右长度最小值+1 
            //注意str.length()前面要加(int),不要会出现错误!! 
            int k=getsearch(0,maxlen,str.length(),i,0); //iseven=0;
            ans=max(ans,2*k+1); 
        } 
        //偶数
        for(int i=0;i<str.length();i++){
            int maxlen=min(i+1,(int)str.length()-1-i)+1; //注意最大的回文长度 (左长为i+1)!!!不知道why
            //注意str.length()前面要加(int),不要会出现错误!! 
            int k=getsearch(0,maxlen,str.length(),i,1);
            ans=max(ans,2*k); 
        } 
        cout<<"max huiwen substr leghth is "<<ans<<endl;
    }
    字符串hash+二分
    //最长回文子串
    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;
    }
    
    • 最大连续子序列和
    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;
    } 
    

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

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

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

    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
    ",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;
    }
    

      

  • 相关阅读:
    让linux下的eclipse支持GBK编码
    Ubuntu 14.04 安装深度音乐的方法(下载速度极慢未成功)
    wine使用
    Sql语句查看表结构
    读数据库所有表和表结构的sql语句
    详解软件工程之软工文档
    浅谈UML的概念和模型之UML九种图
    HTML5 file api读取文件的MD5码工具
    Sublime Text 3 快捷键汇总
    领域驱动设计系列文章汇总
  • 原文地址:https://www.cnblogs.com/shirlybaby/p/12271187.html
Copyright © 2020-2023  润新知