算法和数据结构

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

2020-04-25  本文已影响0人  DecadeHeart

动态规划专题

最大连续子序列求和

//最大连续子序列和,最简单基础班
//状态转移方程 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;
}

上一篇下一篇

猜你喜欢

热点阅读