美文网首页算法和数据结构
机试常用算法和题型-动态规划专题

机试常用算法和题型-动态规划专题

作者: DecadeHeart | 来源:发表于2020-04-25 16:46 被阅读0次

    动态规划专题

    最大连续子序列求和

    //最大连续子序列和,最简单基础班
    //状态转移方程 dp[i]=max{A[i],dp[i-1]+A[i]}
    #include <iostream>
    #include <algorithm>
    using namespace std;
    
    const int maxn=10010;
    int A[maxn],dp[maxn];  //A[i]存放序列,dp[i]存放以A[i]为结尾的联系序列最大和
    int main()
    {
        int n;
        while(cin>>n){
            if(n==0) break;
            for(int i=0;i<n;i++){
                cin>>A[i];
            }
            //确定边界,相当于就是本身就是最大和
            dp[0]=A[0];
            //是从i=1开始的,这是递推,而不是递归
            for(int i=1;i<n;i++){
                dp[i]=max(A[i],dp[i-1]+A[i]);
            }
            //dp[i]存放所有以A[i]为结尾的序列最大和,还需要比较最大值
            int k=0;
            //如何找最大值,排序也可,此处选择比较!!,值得学习
            for(int i=1;i<n;i++){
                if(dp[i]>dp[k]) k=i;
            }
            cout << dp[k] << endl;
        }
    
        return 0;
    }
    //最大连续子序列和进阶版!!还要输出序列的头和尾
    //状态转移方程 dp[i]=max{A[i],dp[i-1]+A[i]}
    #include <iostream>
    #include <algorithm>
    using namespace std;
    
    const int maxn=10010;
    int A[maxn];
    struct node{
        int start,end,sum;
    }dp[maxn];
    
    int main()
    {
        int n,data;
        while(cin>>n){
            if(n==0) break;
            for(int i=0;i<n;i++){
                cin>>A[i];
            }
            //确定边界,相当于就是本身就是最大和
            dp[0].sum=A[0];
            dp[0].start=dp[0].end=0;
            //是从i=1开始的,这是递推,而不是递归
            for(int i=1;i<n;i++){
                    //把max函数拆解后的结果
                if(A[i]>dp[i-1].sum+A[i]){
                    dp[i].start=dp[i].end=i;
                    dp[i].sum=A[i];
                    //就是自身了
                }
                else{
                    dp[i].sum=A[i]+dp[i-1].sum;
                    dp[i].start=dp[i-1].start;
                    dp[i].end=i;
                }
            }
            //dp[i]存放所有以A[i]为结尾的序列最大和,还需要比较最大值
            int k=0;
            //如何找最大值,排序也可,此处选择比较!!,值得学习
            for(int i=1;i<n;i++){
                if(dp[i].sum>dp[k].sum) k=i;
            }
            if(dp[k].sum<0)
                printf("0 %d %d\n",A[0],A[n-1]);
            else
                printf("%d %d %d\n",dp[k].sum,A[dp[k].start],A[dp[k].end]);
        }
    
        return 0;
    }
    
    

    方法二:很巧妙

    
    #include<stdio.h>
    #include<algorithm>
    //#include<windows.h>
    using namespace std;
    int main(){
        int N,i;
        //freopen("input.txt","r",stdin);
        while(scanf("%d",&N)!=EOF){
            long sum=0,Max=-999999999,x;
            for(i=0;i<N;i++){
                scanf("%ld",&x);
                sum=max(sum+x,x);
                Max=max(Max,sum);
            }
            printf("%ld\n",Max);
        }
    }
    
    

    最大加权子矩阵-矩阵压缩

    //最大加权矩形变形,只需要矩阵的和大于k来求矩阵的面积!!
    #include <bits/stdc++.h>
    
    using namespace std;
    #define maxn 150
    int n,m,t;
    int matrix[maxn][maxn];
    int ans=-21000000;
    int temp[maxn];
    int dp[maxn];
    
    void Arrsum(){
       memset(dp,0,sizeof(dp));
    
       for(int i=1;i<=n;i++){
            dp[i]=max(dp[i],dp[i-1]+temp[i]);
            ans=max(ans,dp[i]);
       }
    }
    
    void MatrixSum(){
        for(int i=1;i<=n;i++){
            memset(temp,0,sizeof(temp));
            //这个压缩是所有行分级压缩!!
            for(int j=i;j<=n;j++){
    
                //temp是每一列的和
                for(int k=1;k<=n;k++){
                    temp[k]=temp[k]+matrix[j][k];
                }
                //计算行中最大
                Arrsum();
            }
        }
    
    }
    
    int main()
    {
        cin>>n;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                cin>>matrix[i][j];
            }
        }
        MatrixSum();
        printf("%d\n",ans);
    
        return 0;
    }
    
    

    最长不下降子序列

    //最长不下降子序列,此处不要求连续,只要求是递增,间隔也可,因而双循环
    #include <iostream>
    #include <algorithm>
    using namespace std;
    
    const int N=100;
    int A[N],dp[N];
    
    
    int main()
    {
        int n;
        while(cin>>n){
            for(int i=1;i<=n;i++) cin>>A[i];
            int ans=-1;  //记录最大的dp[i]
            for(int i=1;i<=n;i++){
                dp[i]=1;  //边界初始条件,先假设每个元素自成一格子序列
                //i是最后一个点,就是边界
                for(int j=1;j<i;j++){
                    if(A[i]>=A[j]&&(dp[j]+1>dp[i])){
                        dp[i]=dp[j]+1;
                    }
                }
                ans=max(ans,dp[i]);
            }
            printf("%d",ans);
        }
        return 0;
    }
    

    最长不下降子序列应用

    /*N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学不交换位置就能排成合唱队形。 合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1, 2, …, K,他们的身高分别为T1, T2, …, TK, 则他们的身高满足T1 < T2 < … < Ti , Ti > Ti+1 > … > TK (1 <= i <= K)。*/
    /*动态规划的最长子列问题,分别从前往后和从后往前寻找以i点为尾的最长子列,
    寻找两个子列和的最大值*/
    #include <iostream>
    #include <algorithm>
    using namespace std;
    
    int main()
    {
        int N,i,j,maxn;
        while(cin>>N){
            int high[200],marka[200],markb[200];
            for(int i=1;i<=N;i++){
                cin>>high[i];
                marka[i]=markb[i]=1;
                //每点为尾的子列长度最小都为1
    
            }
    
            for(i=2;i<=N;i++)           /*从前往后寻找以i点为尾的最长递增子列*/
            {
                maxn=marka[i];
                for(j=i-1;j>=1;j--)
                {
                    if(high[i]>high[j])
                        //问题出在这里啊
                        maxn=max(maxn,marka[j]+1);
                        //maxn=(maxn>marka[j]+1)?maxn:marka[j]+1;
                }
                marka[i]=maxn;
            }
            for(i=N-1;i>=1;i--)      /*从后往前寻找以i点为尾的最长递增子列*/
            {
                maxn=markb[i];
                for(j=i+1;j<=N;j++)
                {
                    if(high[i]>high[j])
                       // maxn=(maxn>markb[j]+1)?maxn:markb[j]+1;
                       maxn=max(maxn,markb[j]+1);
                }
                markb[i]=maxn;
            }
            maxn=marka[1]+markb[1];
            //寻找点i两个子列和的最大值
            for(i=2;i<=N;i++){
                maxn=max(maxn,marka[i]+markb[i]);
            }
            cout<<N-maxn+1<<endl;
        }
    
        return 0;
    }
    
    

    最长公共子序列-字符串A,B

    sadstory
    adminsorry
    
    #include <cmath>
    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <string>
    #include <algorithm>
    
    using namespace std;
    
    string str1,str2;
    int dp[1001][1001];
    
    //记住模板
    int lcs(string str1,string str2)
    {
        int len1=str1.size();
        int len2=str2.size();
        //0位已经设置为边界,i and j都从1开始
        for(int i=1; i<=len1; i++)
        {
            for(int j=1; j<=len2; j++)
            {
                if(str1[i-1] == str2[j-1])
                    dp[i][j] = dp[i-1][j-1] + 1;
                else if(dp[i-1][j] > dp[i][j-1])
                    dp[i][j] = dp[i-1][j];
                else
                    dp[i][j] = dp[i][j-1];
            }
        }
        return dp[len1][len2];
    }
    
    int main()
    {
    
        while(cin>>str1)
        {
            cin>>str2;
            //此处为边界,很关键dp[0][j] and d[i][0]均表示字符串0位与其他各字符串的比较
            memset(dp, 0, sizeof(dp));
    
            int ans=lcs(str1, str2);
            printf("%d\n", ans);
        }
        return 0;
    }
    
    

    最长公共子序列求具体LCS

    /*
     dp[i][j] = max(dp[i-1][j], dp[i][j-1],dp[i-1][j-1] + (A[i]==B[j] ? 1 : 0)),表示在这三种状态中取到最大值,
    
    (1)第一种状态表示不录入第一个序列的第i个字符时的最长公共子序列,
    
    (2)第二种状态表示不录入第二个序列的第j个字符时的最长公共子序列,
    
    (3)第三种状态表示第一个序列的前i-1个字符与第二个序列前j-1个字符的公共子序列加上最后一个字符的录入状态,如果最后的一个字符相等则录入状态为1,否则为0。
    
    然后根据动归的状态,来判断我们要求得的序列中的字符有哪些。
    
    */
    #include <cmath>
    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <string>
    #include <algorithm>
    
    using namespace std;
    
    string str1,str2;
    int dp[1001][1001];
    
    //记住模板,在lcs求长度的基础上进行改变,求最长子序列具体
    void lcs(string str1,string str2)
    {
        int len1=str1.size();
        int len2=str2.size();
        //0位已经设置为边界,i and j都从1开始
        for(int i=1; i<=len1; i++)
        {
            for(int j=1; j<=len2; j++)
            {
                if(str1[i-1] == str2[j-1])
                    dp[i][j] = dp[i-1][j-1] + 1;
                else if(dp[i-1][j] > dp[i][j-1])
                    dp[i][j] = dp[i-1][j];
                else
                    dp[i][j] = dp[i][j-1];
            }
        }
    }
    
    void llcs(){
        int i,j,z=0;
        string temp;
        i=str1.size(),j=str2.size();
        //从尾到头的状态进行判断,根据判断可以知道字符加入没有
        while(i!=0&&j!=0){
            if(str1[i-1]==str2[j-1]){
                //只有当两个字符都相等时才可以加入输出字符串
                temp[z++]=str1[--i];
                j--;
            }
            else if(dp[i-1][j]<dp[i][j-1])
                //判断的意思,此时没有相等,且j加的没效果,j不等
                j--;
            else if(dp[i][j-1]<=dp[i-1][j])
                i--;
            //还是看小的,此时是i加的不如j加的有效果,所以减i
        }
        for(i=z-1;i>=0;i--)
            cout<<temp[i];
        cout<<endl;
    }
    
    int main()
    {
    
        while(cin>>str1)
        {
            cin>>str2;
            //此处为边界,很关键dp[0][j] and d[i][0]均表示字符串0位与其他各字符串的比较
            memset(dp, 0, sizeof(dp));
    
            lcs(str1, str2);
            llcs();
        }
        return 0;
    }
    

    hash字符串求最长公共连续子串

    /*
    求最长公共子串(连续)的方法,
    可以用KMP,当然也可以用字符串hash,
    分别计算两个字符串的所有子串的hash值,
    然后一一对比,当两个字符串的hash值相等时,
    如果长度大于之前访问得到的公共子串长度的最大值,
    则更新最大值,并存储此子串的起始位置,
    最终得到最长的公共子串~
    
    */
    
    #include <iostream>
    #include <32/bits/stdc++.h>
    using namespace std;
    
    typedef long long LL;
    const int maxn=1010;
    const LL p=1e7+19;
    const LL mod=1e9+7;
    LL powP[maxn],H1[maxn]={0},H2[maxn]={0};
    
    struct Node{
        int hashValue,length,start,end;
        Node(int a,int b,int c,int d):hashValue(a),length(b),start(c),end(d){};
    };
    
    vector<Node> pr1,pr2;
    
    void init(int len)
    {
        powP[0]=1;
        for(int i=1;i<=len;++i){
            powP[i]=(powP[i-1]*p)%mod;
        }
    }
    
    void calH(LL H[],string &str){
        H[0]=str[0];
        for(int i=1;i<str.length();i++){
            H[i]=(H[i-1]*p+str[i])%mod;
        }
    }
    
    int calSingleSubH(LL H[],int i,int j){
        if(i==0)
            return H[j];
        return ((H[j]-H[i-1]*powP[j-i+1])%mod+mod)%mod;
    }
    
    void calSubH(LL H[],int len,vector<Node> &p){
        for(int i=0;i<len;i++){
            for(int j=i;j<len;j++){
                int value=calSingleSubH(H,i,j);
                p.push_back(Node(value,j-i+1,i,j));
            }
        }
    }
    
    
    string getMax(string str1)
    {
        string str;
        int ans=0;
        for(int i=0;i<pr1.size();++i)
            for(int j=0;j<pr2.size();++j)
            {
                if(pr1[i].hashValue==pr2[j].hashValue)
                {
                    if(pr1[i].length>ans)
                    {
                        ans=pr1[i].length;
                        str=str1.substr(pr1[i].start,pr1[i].end);
                    }
                }
            }
        return str;
    }
    
    int main()
    {
        string str1,str2;
        getline(cin,str1);
        getline(cin,str2);
        init(max(str1.length(),str2.length()));
        calH(H1,str1);
        calH(H2,str2);
        calSubH(H1,str1.length(),pr1);
        calSubH(H2,str2.length(),pr2);
    
        cout << getMax(str1) << endl;
        return 0;
    }
    
    

    最长公共子串是最长公共子序列的的特殊情况

    image image
    public static int lcs(String str1, String str2) {
        int len1 = str1.length();
        int len2 = str2.length();
        int result = 0;     //记录最长公共子串长度
        int c[][] = new int[len1+1][len2+1];
        for (int i = 0; i <= len1; i++) {
            for( int j = 0; j <= len2; j++) {
                if(i == 0 || j == 0) {
                    c[i][j] = 0;
                } else if (str1.charAt(i-1) == str2.charAt(j-1)) {
                    c[i][j] = c[i-1][j-1] + 1;
                    result = max(c[i][j], result);
                } else {
                    c[i][j] = 0;
                }
            }
        }
        return result;
    }
    

    最长回文子序列

    方法一:递归方法,自顶向下

    str[0...n-1]是给定的字符串序列,长度为n,假设lps(0,n-1)表示序列str[0...n-1]的最长回文子序列的长度。

    1.如果str的最后一个元素和第一个元素是相同的,则有:lps(0,n-1)=lps(1,n-2)+2;例如字符串序列“AABACACBA”,第一个元素和最后一个元素相同,其中lps(1,n-2)表示红色部分的最长回文子序列的长度;

    2.如果str的最后一个元素和第一个元素是不相同的,则有:lps(0,n-1)=max(lps(1,n-1),lps(0,n-2));例如字符串序列“ABACACB”,其中lps(1,n-1)表示去掉第一元素的子序列,lps(0,n-2)表示去掉最后一个元素的子序列。

    #include <iostream>
    #include <string>
    #include <algorithm>
    using namespace std;
    
    int lps(string str,int i,int j){
        //递归出口
        if(i==j)
            return 1; //只有一个元素,回文长度为1
        if(i>j)
            return 0; //字符序列str[i...j]
    
        //如果首尾相同
        if(str[i]==str[j])
            return lps(str,i+1,j-1)+2;
        //如果首尾不相同
        return max(lps(str,i,j-1),lps(str,i+1,j));
    
    }
    
    int main()
    {
        string str;
        while(cin>>str){
            int n=str.size();
            int ans=lps(str,0,n-1);
            cout<<ans<<endl;
        }
        return 0;
    }
    
    

    方法二:自底向上动态规划方法

    #include <iostream>
    #include <algorithm>
    #include <string>
    #include <cstring>
    using namespace std;
    
    int dp[1000][1000];
    
    int lpsDp(string str,int n){
        int temp;
        memset(dp,0,sizeof(dp));
        //边界条件
        for(int i=0;i<n;i++)
            dp[i][i]=1;
    
        //dp[j][i]的含义表示首为j,尾为i
        for(int i=1;i<n;i++){
            temp=0;
            //考虑所有连续的长度为i+!的子串,str[j...j+i
            for(int j=0;j+i<n;j++){
                //如果首尾相同
                if(str[j]==str[j+i])
                    temp=dp[j+1][j+i-1]+2;
                else
                    temp=max(dp[j+1][j+i],dp[j][j+i-1]);
                dp[j][j+i]=temp;
            }
        }
        //返回字符串str[0....n-1]的最长回文子序列长度
        return dp[0][n-1];
    }
    int main()
    {
        string str;
        while(cin>>str){
            int ans=lpsDp(str,str.size());
            cout<<ans<<endl;
        }
        return 0;
    }
    
    

    最长回文子串,连续的!!

    动态规划法:

    回文字符串的子串也是回文,比如P[i,j](表示以i开始以j结束的子串)是回文字符串,那么P[i+1,j-1]也是回文字符串。这样最长回文子串就能分解成一系列子问题了。这样需要额外的空间O(N2),算法复杂度也是O(N2)。

    首先定义状态方程和转移方程:

    P[i,j]=false:表示子串[i,j]不是回文串。P[i,j]=true:表示子串[i,j]是回文串。

    P[i,i]=true:当且仅当P[i+1,j-1] = true && (s[i]==s[j])

    否则p[i,j] =false;

    PATZJUJZTACCBCC
    
    #include <iostream>
    #include <string>
    #include <cstring>
    using namespace std;
    
    bool dp[1000][1000];
    
    int maxlength=0;
    string flp(string s){
        int len=s.size();
        int start=0;
        //子串长度为1和2的初始化,边界条件
        for(int i=0;i<len;i++){
            dp[i][i]=true;
            if(i<len-1&&s[i]==s[i+1]){
                dp[i][i+1]=true;
                start=i;
                maxlength=2;
            }
        }
        //使用上述结果可以dp出子串长度为3~len-1的子串
        for(int strlen=3;strlen<len;strlen++){
            for(int i=0;i<=len-strlen;i++){
                int j=i+strlen-1; //子串结束位置
                if(dp[i+1][j-1]&&s[i]==s[j]){
                    dp[i][j]=true;
                    maxlength=strlen;
                  //这个start的记忆就很灵性
                    start=i;
                }
            }
        }
        if(maxlength>0)
            return s.substr(start,start+maxlength-1);
        return NULL;
    
    }
    
    
    int main()
    {
        string str;
        while(cin>>str){
            memset(dp,0,sizeof(dp));
            string ans=flp(str);
            cout<<ans<<endl;
            cout<<maxlength<<endl;
        }
        return 0;
    }
    
    

    暴力法:

    遍历字符串S的每一个子串,去判断这个子串是不是回文,是回文的话看看长度是不是比最大的长度maxlength大。遍历每一个子串的方法要O(n2),判断每一个子串是不是回文的时间复杂度是O(n),所以暴利方法的总时间复杂度是O(n3)。

    PATZJUJZTACCBCC
    #include <iostream>
    #include <string>
    using namespace std;
    
        int maxlength=0;
    string flp(string str){
        int len=str.size();
    
        int start=0;
    
            for(int i = 0; i < len; i++){
                for(int j = i + 1; j < len; j++){
                    int index1 = 0;
                    int index2 = 0;
                    // 对每个子串都从两边开始向中间遍历
                    for(index1 = i, index2 = j; index1 < index2; index1 ++, index2--)                   {
                        if(str[index1] != str[index2])
                            break;
                    }
                    // 若index1=index2,表示串是类似于abcba这种类型;若大于,则是abccba这种类型
                    if(index1 >= index2 && j - i > maxlength){
                        maxlength = j - i + 1;
                        start = i;
                    }
                }
    
            }
    
        if(maxlength>0)
            return str.substr(start,start+maxlength-1);
        return NULL;
    }
    
    int main()
    {
        string str;
        while(cin>>str){
            string ans=flp(str);
    
            cout << ans<< endl;
            cout << maxlength<< endl;
        }
    
        return 0;
    }
    
    

    中心扩展法:

    中心扩展就是把给定的字符串的每一个字母当做中心,向两边扩展,这样来找最长的子回文串。算法复杂度为O(N^2)。

    但是要考虑两种情况:

    1、像aba,这样长度为奇数。

    2、想abba,这样长度为偶数

    #include <iostream>
    #include <string>
    using namespace std;
    
    string flp(string str){
        int len=str.size();
        int maxlength=0,start=0;
        //先处理aba情况
        for(int i=0;i<len;i++){
            int j=i-1;
            int k=i+1;
            while(j>=0 && k<len && str[j]==str[k]){
                if(k-j+1>maxlength){
                    maxlength=k-j+1;
                    start=j;
                }
                j--;
                k++;
            }
        }
    
        //类似于abba情况,以i,i+!为中心两边扩展
        for(int i=0;i<len;i++){
            int j=i;
            int k=i+1;
            while(j>=0 && k<len&&str[j]==str[k]){
                if(k-j+1>maxlength){
                    maxlength=k-j+1;
                    start=j;
                }
                j--;
                k++;
            }
        }
        if(maxlength>0)
            return str.substr(start,start+maxlength-1);
    }
    
    int main()
    {
        string str;
        while(cin>>str){
            string ans=flp(str);
    
            cout << ans<< endl;
    
        }
        return 0;
    }
    

    判断是否为回文串:

    #include <iostream>
    #include <string>
    #include <vector>
    
    using namespace std;
    //从中间往两边扫,就有两种情况
    int main()
    {
        string str;
        while(cin>>str){
            int len=str.size();
            int mid =len/2;
            bool flag=true;
            if(len%2==1){
                for(int i=0;mid+i<len;i++){
                    if(str[mid-i]!=str[mid+i])
                    {
                        flag=false;
                        break;
                    }
                }
            }else{
                for(int i=0;mid+i<len;i++){
                    if(str[mid+i]!=str[mid-i-1])
                    {
                        flag=false;
                        break;
                    }
                }
    
            }
            if(flag) cout<<"YES"<<endl;
            else cout<<"NO"<<endl;
        }
    
        return 0;
    }
    
    //或者从两端往中间扫
    #include <cstdio>
    #include <cstring>
    int main()
    {
        char a[300];
        while(gets(a))
        {
            int len=strlen(a);
            bool flag=1;
            for(int i=0;i<len/2;i++)
            {
                if(a[i]!=a[len-i-1])
                {
                    flag=0;
                    break;
                }
            }
            printf("%s\n",flag?"YES":"NO");
        }
        return 0;
    } 
    

    实际应用,输出回文子串

    Confuciuss say:Madam,I'm Adam.
    //最长回文子串暴力法
    #include <iostream>
    #include <string>
    #include <cstring>
    #include <ctype.h>
    
    using namespace std;
    
    bool dp[5010][5010];
    int pos[5010];
    string str;
    
    string flp(char s[]){
        int maxlength=0;
        int len=strlen(s);
        int start=0,end=0;
        //子串长度为1和2的初始化,边界条件
        //dp[i][j] 表示以i开始,j结尾的子串是否是回文字符串!!,则dp[i+1][j-1]也是回文字符串
        for(int i=0;i<len;i++){
            dp[i][i]=true;
            if(i<len-1&&s[i]==s[i+1]){
                dp[i][i+1]=true;
                start=i;
                maxlength=2;
            }
        }
        //使用上述结果可以dp出子串长度为3~len-1的子串
        for(int strlen=3;strlen<len;strlen++){
            for(int i=0;i<=len-strlen;i++){
                int j=i+strlen-1; //子串结束位置
                if(dp[i+1][j-1]&&s[i]==s[j]){
                    dp[i][j]=true;
                    maxlength=strlen;
                    start=pos[i];
                    end=pos[j];
                }
            }
        }
        if(maxlength>0)
            return str.substr(start,end-start+1);
        return NULL;
    
    }
    
    
    int main()
    {
        //终于知道哪里错了
        while(getline(cin,str)){
            memset(dp,0,sizeof(dp));
            memset(pos,0,sizeof(pos));
            char temp[5010];
            int num=0;
            for(int i=0;i<str.size();i++){
                if(isalpha(str[i]) || isdigit(str[i])){
                    temp[num]=toupper(str[i]);
                    pos[num]=i;
                    num++;
                }
            }
            temp[num]='\0';
            cout<<temp;
            cout<<endl;
            string ans=flp(temp);
            cout<<ans<<endl;
        }
        return 0;
    }
            for(int i=0;i<str.size();i++){
                if(isalpha(str[i]) || isdigit(str[i])){
                    char c=toupper(str[i]);
                    temp=temp+c;
                    pos[num]=i;
                    num++;
                }
            }
    

    01背包问题

    /*
    01背包
    5 8  //n==5,V==8
    3 5 1 2 2 //w[i]
    4 5 2 1 3 //c[i]
    //表示用重量8达到最大价值c
    
    分为两种策略,
    不放第i件物品,即i-1件物品满足条件 dp[i-1][v]
    放第i件物品  dp[i-1][v-w[i]]+c[i]
    
    又由于dp[i][v]总是只需要dp[i-1][v]左半部分
    故可以直接开一个dp[v]数组
    但是枚举方向该位i从1到n,v从V到0(逆序!!!)
    dp[v]=max(dp[v],dp[v-w[i]]+c[i])
    
    */
    
    #include <iostream>
    #include <algorithm>
    using namespace std;
    
    const int maxn=100;
    const int maxv=1000;
    
    int w[maxn],c[maxn],dp[maxv];
    
    int main()
    {
        int n,V;
        cin>>n>>V;
    
        for(int i=0;i<n;i++){
            cin>>w[i];
        }
        for(int i=0;i<n;i++){
            cin>>c[i];
        }
        //边界
        for(int v=0;v<=V;v++){
            dp[v]=0;
        }
    
        for(int i=0;i<n;i++){
            for(int v=V;v>=w[i];v--){
                dp[v]=max(dp[v],dp[v-w[i]]+c[i]);
            }
        }
    
        int maxans=0;
        for(int v=0;v<=V;v++){
            if(dp[v]>maxans){
                maxans=dp[v];
            }
        }
        cout<<maxans<<endl;
    
        return 0;
    }
    
    

    完全背包-拆分整数成2的幂的和

    
    /*这道题确实可以用动态规划来解,它其实是一个完全背包求恰装满背包时的方案总数
    问题.具体是,因为每一个拆分必须是1,2,4,2^3,...2^19(考虑n最大为10^6),
    所以对于一个整数n,看它的这种拆分数有多少个,就相当于现在有20种物品,第i种物品
    的花费是2^(i-1),每一种可以重复取, dp[i][j]表示前i种物品恰装满容量为j的物品时
    的方案总数,从而dp[i][j] = dp[i-1][j] + dp[i][j-a[i]]
    */
    
    #include <iostream>
    #include <bits/stdc++.h>
    using namespace std;
    
    int n,dp[1000002],a[21];
    
    int main()
    {
        int i,j,t;
        for(i=1;i<=20;i++){
            a[i]=(1 << (i-1));
        }
        dp[0]=1;
        while(cin>>n){
            memset(dp+1,0,sizeof(dp));
            for(i=1;i<=20;i++){
                for(j=a[i];j<=n;j++){
                        //终于明白滚动数字的含义,二维降一维
                    dp[j]=dp[j]+dp[j-a[i]];
                    dp[j]=dp[j]%1000000000;
                }
            }
            cout<<dp[n]<<endl;
        }
    
        return 0;
    }
    
    
    
    

    动态规划解决括号最长匹配长度

    /*方法一没怎么看懂,不过dp[i]应当表示以i号开头时最长长度*/
    #include <iostream>
    #include <bits/stdc++.h>
    using namespace std;
    
    int main()
    {
        string str;
        cin>>str;
        int maxn=0;
        vector<int> dp(str.size(),0);
        //vector高级用法,快速初始化
    
        for(int i=str.size()-2;i>=0;i--){
            if(str[i]=='('){
                int j=i+1+dp[i+1];
                if(j<str.size()&&str[j]==')'){
                    dp[i]=dp[i]+dp[i+1]+2;
                    if(j+1<str.size())
                        dp[i]=dp[i]+dp[j+1];
                }
                if(maxn<dp[i])
                    maxn=dp[i];
               }
        }
    
        cout <<maxn << endl;
        return 0;
    }
    
    

    方法二:更容易理解,但是一用例居然没有通过

    #include <iostream>
    #include <bits/stdc++.h>
    using namespace std;
    
    int maxAvailiable(string str){
        int maxn=0;
        int i,j;
        int len=str.size();
    
        int *dp=(int *)malloc(sizeof(int)*len);
        memset(dp,0,sizeof(int)*len);
    
        //至少有两个才可以匹配,故从i=2开始
        for(int i=2;i<len;i++){
            //这个才是以i结尾,j是匹配头部
            if(str[i]==')'){
                j=i-1-dp[i-1];
            //减去i-1匹配的
                if(j>=0&&str[j]=='('){
                    dp[i]=dp[i-1]+2;
                    if(j>0)
                        //终于懂这里了,j前面的也算是匹配了
                        dp[i]=dp[i]+dp[j-1];
                }
                maxn=maxn<dp[i]?dp[i]:maxn;
            }
    
        }
        free(dp);
        return maxn;
    
    }
    
    int main()
    {
        string str;
        cin>>str;
    
        cout <<maxAvailiable(str)<< endl;
        return 0;
    }
    
    

    动态规划花钱类问题两种思路

    动态规划自底向上方法

    /*
    若干邮票,选取最小张数凑成一个给定总值
    不是贪心算法,居然是0-1背包,求的是最小数量
    
    dp[i][j]表示i个邮票,j表示总量面额,dp表示最小邮票数量
    */
    
    /*
        最少邮票数 >> 01动态规划
    
        状态
        集合中数字
        dp[i][j]    0   1   2   3   4   5   6   7   8   9   10
        1           0   1   ∞   ∞   ∞   ∞   ∞   ∞   ∞   ∞   ∞
        1 3         0   1   ∞   1   2   ∞   ∞   ∞   ∞   ∞   ∞
        1 3 3       0   1   ∞   1   2   ∞   2   3   ∞   ∞   ∞
        1 3 3 3     0   1   ∞   1   2   ∞   2   ∞   ∞   3   4
        1 3 3 3 4   0   1   ∞   1   2   2   2   2   3   3   3
    
        状态迁移方程
        dp[j] = min{dp[j],dp[j-stamp[i]]+1}
        其中dp[j-stamp[i]]+1,表示将第i个邮票加入集合后 凑总量为j的面额 所需要的最少邮票数量
    */
    
    #include <iostream>
    #include <bits/stdc++.h>
    using namespace std;
    #define INF 1000
    int stamp[1000];
    int dp[1000];
    
    int minStamp(int num,int deno){
        //邮票个数和总额
        int i,j;
    
        //状态全部初始化
        for(j=0;j<=deno;j++){
            dp[j]=(j==0)?0:INF;
        }
        for(i=0;i<num;i++){
            //从后往前寻找若能凑成,且使数量变少就使用
            for(j=deno;j>=stamp[i];j--){
                if(dp[j-stamp[i]]!=INF)
                    dp[j]= (dp[j]<dp[j-stamp[i]]+1)? dp[j]:dp[j-stamp[i]]+1;
    
            }
        }
        return dp[deno]==INF?0:dp[deno];
    }
    
    int main()
    {
        int num,deno;
        while(cin>>deno>>num){
            for(int i=0;i<num;i++)
                cin>>stamp[i];
            cout<<minStamp(num,deno);
        }
    
        return 0;
    }
    
    

    递归方法,自顶向下:

    /*
    递归法解决问题,自顶向下方法,
    动态规划确实自底向上!!一个问题两个方向
    
    */
    #include <iostream>
    
    using namespace std;
    int M;  //邮票总价值
    int N; //n张邮票
    int minn; //最小票数
    
    void compute(int data[],int m,int n,int num){
        //m是递归中几张邮票面值的和,n表示data还未参与递归的最高位,num表示m由num张邮票相加得到
        int i;
        if(m==M){
            if(num<minn) minn=num;
            return;
        }
        else if(m>M) return;
        for(i=n;i>=0;i--)
        //更好理解
            compute(data,m+data[i],i-1,num+1);
    }
    
    int main()
    {
        int i,j,k;
        while(cin>>M>>N){
            minn=N+1;
            int data[20];
            for(i=0;i<N;i++){
                cin>>data[i];
            }
            compute(data,0,N-1,0);
    
            if(minn==N+1)
                //如果可以凑成M,则minn<=N;
                printf("0\n");
            else
                printf("%d\n",minn);
        }
    
        return 0;
    }
    
    

    递归+动态规划解决组合数问题

    /*
    放苹果,递归+动态规划
    把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,
    问共有多少种不同的分法?
    (用K表示)5,1,1和1,5,1 是同一种分法。
    
    */
    
    
    /*
        M个苹果放在N个盘子里分法有:dp[M][N], 0 <= M,N <= 10
        设dp(m,n) 为m个苹果,n个盘子的放法数目,则先对n作讨论,
        当m<n:必定有n-m个盘子永远空着,去掉它们对摆放苹果方法数目不产生影响。dp(m,n) = dp(m,m)
        当m>=n:不同的放法可以分成两类:
            1、有至少一个盘子空着,即相当于dp(m,n) = dp(m,n-1);
            2、所有盘子都有苹果,相当于可以从每个盘子中拿掉一个苹果,不影响不同放法的数目,即dp(m,n) = dp(m-n,n).
            而总的放苹果的放法数目等于两者的和,即 dp(m,n) =dp(m,n-1)+dp(m-n,n)
        初始条件说明
            当m=0,n=0时,没苹果,没盘子,定为0种放法。这个条件在计算的时候用不到。题设至少一个盘子一个苹果。
            当m=0,n>0时,没苹果,有盘子,定为1种放法。这个有点抽象,考虑:dp[1][1]=dp[1][0]+dp[0][1]=0+1。
            当m>0,n=0时,有苹果,没盘子,定为0种放法。
            dp两条路,第一条n会逐渐减少,终会到达出口n=0;
            第二条m会逐渐减少,因为n>m时,会计算dp(m,m) 所以终会到达出口m=0.
    */
    
    /*#include <iostream>
    
    using namespace std;
    
    int dfs(int m,int n){
        if(m>=0&&n==0)
            return 0;
        else if(m==0&&n>0)
            return 1;
        else if(m>=n)
            return dfs(m,n-1)+dfs(m-n,n);
        else
            return dfs(m,m);
    }
    
    int main()
    {
        int m,n;
        while(cin>>m>>n){
            cout<<dfs(m,n)<<endl;
        }
        return 0;
    }
    */
    
    #include <iostream>
    using namespace std;
    int dp[11][11];
    
    int main(){
    
        int m,n;
    
        while(cin>>m>>n){
            for(int i=1;i<=m;i++){
                dp[i][0]=0;
            }
    
            for(int j=1;j<=n;j++){
                dp[0][j]=1;
            }
    
            for(int i=1;i<=m;i++){
                for(int j=1;j<=n;j++){
                    if(i>=j)
                        dp[i][j]=dp[i][j-1]+dp[i-j][j];
                    else
                        dp[i][j]=dp[i][i];
                }
            }
            cout<<dp[m][n]<<endl;
        }
        return 0;
    }
    
    

    相关文章

      网友评论

        本文标题:机试常用算法和题型-动态规划专题

        本文链接:https://www.haomeiwen.com/subject/xtukwhtx.html