动态规划整理

动态规划的基本思想:用一个表来记录所有已解决的子问题的答案。适用于解最优化问题,且是自底向上的。
1.分解最优结构
得到最优解的前提是子问题也是最优解
2.建立递归关系
3.计算最优值

记忆化搜索:记录下来,如果已经计算过就不再计算
uva674
题意:有1,5,10,25,50五种硬币,给出一个数字,问又几种凑钱的方式能凑出这个数。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 8000;
const int coin[5] = {1,5,10,25,50};
int n;
long long dp[maxn][5];

long long solve(int i, int s) {

    int j;
    if(dp[s][i] != -1) return dp[s][i];
    dp[s][i] = 0;
    for(j=i; j<5&&s>=coin[j]; j++) {

        dp[s][i] += solve(j,s-coin[j]);
    }
    return dp[s][i];
}
int main() {
    int i;
    memset(dp, -1, sizeof(dp));
    for(i=0; i<5; i++) dp[0][i]=1;//0的时候实际上是花了一个硬币,所以是1
    while(scanf("%d", &n) != EOF) {
        printf("%lld\n", solve(0,n));
    }

    return 0;
}

可以看出来记忆化搜索是自顶向下的
这题同样可以递推解决(自底向上)

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int maxn = 8000;
int n, coin[5]={1,5,10,25,50};
long long dp[maxn] = {1};


int main() {
    int i, j;
    for(i=0; i<5; i++)
        for(j=0; j<maxn-100; j++) {
            dp[j+coin[i]] += dp[j];
        }
    while(scanf("%d", &n) != EOF) {

        printf("%lld\n",dp[n]);
    }
    return 0;
}

段最大和问题
uva507
题目大意:
Jill喜欢骑自行车,但是自从他的城市有了公交系统后,他就比较少骑车了,于是他买了一个折叠自行车,这样他就可以把自行车带到公交车上,当他下车后,他就可以继续骑车。
现在b条道路,每条道路有n个公交站点,两个站点间都有一个喜欢值,现在问你能否求出,哪两个站点间的喜欢值最大,如果有多个最大值,输出其中距离最远的站点。

#include<cstdio>
#include<string>
using namespace std;
int s[20005];
int snum;
int main()  {
    int n, i, kase=0;
    scanf("%d", &n);
    while(n--) {
        kase++;
        scanf("%d", &snum);
        for(i=1; i<=snum-1; i++)
            scanf("%d", &s[i]);
        int st, l, r, sum, maxs;
        st=l=r=1;
        sum=maxs=0;
        for(i=1; i<snum; i++) {
            sum += s[i];
            if(sum<0) {
                sum=0;
                st=i+1;
            }
            if(sum>maxs || (sum==maxs&&(i-st)>(r-l))) {
                maxs=sum;
                l=st;
                r=i;
            }
        }
        if(maxs <= 0) {
            printf("Route %d has no nice parts\n", kase);
        }
        else {
            printf("The nicest part of route %d is between stops %d and %d\n", kase, l, r+1);
        }
    }
    return 0;
}

二维最大和
UVA116:
给你一个n*m的数字表格,找到一条从左到右的路径,使得上面的数字和最小,可越界。
自底向下:从终点开始枚举起点,保证每个子问题都是最优解

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
using namespace std;
const int maxn = 105;
const int INF = 0x3f3f3f3f;
int map[15][maxn];
int dp[15][maxn];
const int dir[3][2]={{0,1},{1,1},{-1,1}};
int n, m;
int path[15][maxn];
void print(int cur, int y) {

    if(y != m-1) {
        printf(" %d",path[cur][y]+1);
        print(path[cur][y],y+1);
    }
}
int Dp(int x, int y) {

    int i;
    if(y>=m) return dp[x][y]=0;
    if(dp[x][y] != INF) return dp[x][y];
    int a, b;
    a = x+dir[0][0];
    b = y+dir[0][1];
    if(a == -1) a=n-1;
    if(a == n) a=0;
    dp[x][y] = Dp(a,b)+map[x][y];
    path[x][y] = a;

    int temp = INF;
    for(i=1; i<3; i++) {
        a=x+dir[i][0];
        b=y+dir[i][1];
        if(a == -1) a=n-1;
        if(a == n) a=0;
        temp = Dp(a,b)+map[x][y];
        if(temp == dp[x][y]) {
            path[x][y] = path[x][y]<a?path[x][y]:a;
        }
        else if(temp < dp[x][y]) {
            dp[x][y] = temp;
            path[x][y] = a;
        }
    }
    return dp[x][y];
}

int main() {
    int i, j;
    while(scanf("%d%d", &n, &m) != EOF) {

        for(i=0; i<n; i++)
            for(j=0; j<m; j++) {

                scanf("%d", &map[i][j]);
            }
        for(i=0; i<=n; i++)
            for(j=0; j<=m; j++) {
                dp[i][j]=INF;
                path[i][j]=n;
            }
        int ans = INF;
        int temp, r;
        for(i=n-1; i>=0; i--) {
            temp=Dp(i,0);
            if(temp <= ans) {
                ans = temp;
                r=i;
            }
        }
        printf("%d", r+1);
        print(r,0);
        printf("\n%d\n", ans);

    }
    return 0;
}

区间DP,矩阵连乘问题
题目:矩阵连乘,求最小运算次数,输出运算优先级(用括号给出)。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn = 15;
const int INF = 0x7fffffff;
int x[maxn],y[maxn];
int d[maxn][maxn], r[maxn][maxn];
int dp(int a, int b) {

    if(d[a][b]!=-1) return d[a][b];
    r[a][b]=a;
    int t,i;
    if(a==b) return d[a][b]=0;
    d[a][b] = INF;
    for(i=a; i<b; i++) {
        t=dp(a,i)+dp(i+1,b)+x[a]*y[i]*y[b];
        if(t<d[a][b]) {

            d[a][b]=t;
            r[a][b]=i;
        }
    }
    return d[a][b];
}
void print(int a,int b){

    if(a>b) return;
    if(a==b) printf("A%d",a+1);
    else{
        printf("(");
        print(a,r[a][b]);
        printf(" x ");
        print(r[a][b]+1,b);
        printf(")");
    }

}
int main() {
    int n,cas=0,i;
    while(scanf("%d", &n) && n) {

        memset(d, -1, sizeof(d));
        for(i=0; i<n; i++)
            scanf("%d%d", &x[i],&y[i]);
        dp(0,n-1);
        printf("Case %d: ", ++cas);  
        print(0,n-1);
        puts("");
    }
    return 0;
}

最长子序列:
UVA10405:求第二串相对于第一串的最长序列长度(模板题)(递推:有起点终点)

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
using namespace std;
const int maxn = 1005;
string s1,s2;
int f[maxn][maxn];
int main() {
    int i,j;
    while(getline(cin,s1)) {
        getline(cin,s2);
        memset(f,0,sizeof(f));
        for(i=1; i<=s1.size(); i++)
            for(j=1; j<=s2.size(); j++) {
                if(s1[i-1]==s2[j-1]) {
                    f[i][j]=f[i][j]>(f[i-1][j-1]+1)?f[i][j]:f[i-1][j-1]+1;
                }
                else {
                    f[i][j] = f[i-1][j]>f[i][j-1]?f[i-1][j]:f[i][j-1];
                }
            }
        printf("%d\n",f[s1.size()][s2.size()]);
    }


}

这题用scanf过不了,逻辑是一样的,就是输入改成scanf,对应的是字符串数组不是string,就是过不了,不知道为什么

UVA531
题意:找出两段话连续最长的单词组
主要学习如何输出,就是把I,J用计算方法记录下来,然后的话所有被跳过的单词都是这个值,也就是说,这个算出来的IJ减一的话是上一个找到的单词的位置

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
using namespace std;
char w1[105][32];
char w2[105][32];
int dp[105][105];
int f[105][105];
int num1, num2;
void print(int s, int flag) {
    int a=s/1000;
    int b=s%1000;
    if(a && b) {

        print(f[a-1][b-1], 1);
        printf("%s", w1[a-1]);
        if(flag) printf(" ");
    }

}
int main() {
    int i,j;

    while(cin>>w1[0]) {
        num1=1,num2=0;
        if(strcmp(w1[0],"#")) {
            while(cin>>w1[num1] && strcmp(w1[num1],"#")) {

                num1++;
            }

        }
        else num1=0;
        while(cin>>w2[num2] && strcmp(w2[num2],"#")) {

            num2++;
        }
        memset(dp, 0, sizeof(dp));
        memset(f, 0, sizeof(f));
        for(i=1; i<=num1; i++)
            for(j=1; j<=num2; j++) {
                if(strcmp(w1[i-1],w2[j-1])==0) {
                    dp[i][j] = dp[i-1][j-1]+1;
                    f[i][j] = i*1000+j;

                }
                else if(dp[i-1][j]<dp[i][j-1]) {
                    dp[i][j] = dp[i][j-1];
                    f[i][j] = f[i][j-1];
                }
                else {
                    dp[i][j] = dp[i-1][j];
                    f[i][j] = f[i-1][j];
                }
            }
            if(f[num1][num2]) {

                print(f[num1][num2],0);
            }
            printf("\n");
    }


    return 0;
}

DAG最长路

UVA10131
题意:一群大象,每头大象有两个属性1:体重 2:IQ,现在从这些大象里挑出大象来排队,队伍有两个要求,每一头大象的体重必须比前一头大,IQ必须比前一头小,求此队伍的最长长度,并列出队伍中大象的序号,序号按照输入给的顺序,序号从1开始
这题相对上一题有更多的限制条件,道理是一样的,用的记忆化搜索(没有起点和终点)

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 1005;
struct node {
    int w;
    int v;
}e[maxn];
int f[maxn];
int path[maxn];
int num;
int ans;
int dp(int cur) {
    if(f[cur]) return f[cur];
    int a;
    for(int i=0; i<num; i++)
        if(e[i].w > e[cur].w && e[i].v<e[cur].v) {
            a = dp(i)+1;
            if(f[cur] <= a) {
                f[cur] = a;
                path[cur] = i;
            }
        }
    return f[cur];
}


int main() {
    int a, b;
    num = 0;    
    while(scanf("%d%d", &a, &b)!=EOF) {
        e[num].w = a;
        e[num].v = b;
        num++;
    }
    for(int i=0; i<num; i++) {
        path[i] = i;
    }
    ans = 0;
    int flag;
    for(int i=0; i<num; i++) {
        if(!f[i]) 
            dp(i);
        if(ans < f[i]) {
            ans = f[i];
            flag = i;
        }
    }

    printf("%d\n", ans+1);
    int i;
    for(i=flag; path[i]!=i; i=path[i]) {
        printf("%d\n", i+1);
    }
    printf("%d\n", i+1);

    return 0;
}

uva437
题目:
有n种长宽高为x,y,z的砖头,每种都有无数个。
砖头可以用不同姿势的方向来盖。
砖头a以某种姿势可以盖在砖头b上,当且仅当a的底部的长宽都要比b的底部长宽要小。
问最高可以建多高?
思路和上一题一样的,满足条件的进行递归,记忆化搜索,就是要懂得转化为普通的问题,也就是暴利思想,把所有情况都存储起来
代码:

#include<cstdio>
#include<cstring>
#include<string>
#include<iostream>
using namespace std;
struct stone{
    int x, y, z;
}s[35*3];
int m[35*3][35*3];
int h, ans;
int dp[300];
bool check(int i, int j) {
    if((s[i].x < s[j].x && s[i].y < s[j].y) || (s[i].x < s[j].y && s[i].y < s[j].x)) return true;
    return false;
}
int dfs(int cur) {
    if(dp[cur] != -1) {
//      printf("%d\n", dp[cur]);
        return dp[cur];
    }
    dp[cur] = s[cur].z;
    for(int i=0; i<h; i++)
        if(m[cur][i]) dp[cur] = max(dp[cur], dfs(i)+s[cur].z);
//  printf("%d\n", dp[cur]);
    return dp[cur];
}
int main() {
    int n, i, j, kase=0;
    while(scanf("%d", &n) && n) {
        int x, y, z;
        kase++;
        h = 0;
        for(i=1; i<=n; i++) {
            scanf("%d %d %d",&x, &y, &z);
            s[h].x = x, s[h].y = y, s[h].z = z;
            h++;
            s[h].x = x, s[h].y = z, s[h].z = y;
            h++;
            s[h].x = y, s[h].y = z, s[h].z = x;
            h++;

        }
        memset(m, 0, sizeof(m));
        for(i=0; i<h; i++)
            for(j=i+1; j<h; j++) {
                if(check(i, j)) {
                    m[i][j] = 1;

                }
                if(check(j, i)) {
                    m[j][i] = 1;
                }
            }
        memset(dp, -1, sizeof(dp));
        ans = 0;
        for(i=0; i<h; i++)
            ans = max(ans, dfs(i));
        printf("Case %d: maximum height = %d\n", kase, ans);
    }
    return 0;
}

背包问题:
0-1背包
uva562,把硬币分成两堆,两堆的差值尽量小
思路:
就是把总值/2,然后尽量靠近这个值,每个硬币只能用一次,所以是0-1背包

#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
const int maxn = 105;
int coin[maxn];
int d[maxn][maxn*500];

int main() {
    int t,i,j;
    int num;
    int ssum;
    scanf("%d", &t);
    while(t--) {

        ssum=0;
        memset(d,0,sizeof(d));
        memset(coin,0,sizeof(coin));
        scanf("%d",&num);
        for(i=1;i<=num;i++) {
            scanf("%d",&coin[i]);
            ssum+=coin[i];
        }
        int sum = ssum/2;
        for(i=1; i<=num; i++) {
            for(j=0; j<=sum; j++) {
                d[i][j]=d[i-1][j];
                if(j>=coin[i]) {
                    d[i][j]=d[i][j]>(d[i-1][j-coin[i]]+coin[i])?d[i][j]:(d[i-1][j-coin[i]]+coin[i]);
                }
            }
        }
        printf("%d\n",ssum - d[num][sum] - d[num][sum]);  


    }
    return 0;
}

完全背包
uva357,类似于uva674

#include<cstdio>
#include<string>
#include<cstring>
using namespace std;
long long count[32002];
int table[5] = {1, 5, 10, 25, 50};
long long dp(int n) {
    count[0] = 1;
    int i, j;
    for(i=0; i<5; i++)
        for(j=table[i]; j<=n; j++)
            count[j] += count[j-table[i]];
    return count[n];
}
int main() {
    int n;  
    while(scanf("%d", &n)!=EOF) {
        memset(count, 0, sizeof(count));
        long long ans = 0;
        ans = dp(n);
        if(ans > 1)
            printf("There are %lld ways to produce %d cents change.\n", ans, n);
        else if(ans == 1) 
            printf("There is only 1 way to produce %d cents change.\n", n);

    }


    return 0;

}

uva624(0-1背包)
题目:
给不一组数,以及一个目标值,选出相加后最接近这个值得数,每个数用1次

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
using namespace std;
int n,tn;
const int maxn = 10005;
int t[25];
int dp[maxn];
int path[25][maxn];


int main() {
    int i,j;
    while(scanf("%d", &n) != EOF) {

        scanf("%d", &tn);
        for(i=0; i<tn; i++) {
            scanf("%d", &t[i]);
        }
        memset(dp, 0, sizeof(dp));
        memset(path, 0, sizeof(path));
        int flag;
        for(i=0; i<tn; i++)
            for(j=n; j>=t[i]; j--) {

                if(dp[j]<dp[j-t[i]]+t[i]) {
                    dp[j] = dp[j-t[i]]+t[i]; 
                    path[i][j] = 1;

                }

            }
            for(i=tn, j=n; i>=0; i--) {
                if(path[i][j]) {
                    printf("%d ",t[i]);
                    j -= t[i];
                }
            }

        printf("sum:%d\n", dp[n]);
    }

    return 0;
}

uva10465
完全背包,两种汉堡,各有耗时,求在浪费时间最少的情况下吃的汉堡最多
由于必须是从头开始连续的,所以dp要清成负数,保证并不会有没吃汉堡的时间里与之前吃了汉堡的时间一样的情况


#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
using namespace std;
int dp[10005];
int main() {
    int m, n, t, i, j;
    while(scanf("%d%d%d", &n, &m, &t) != EOF) {

        memset(dp, -10001, sizeof(dp));
        dp[0]=0;

        for(i=m; i<=t; i++) {
            if(dp[i]<dp[i-m]+1) {
                dp[i] = dp[i-m]+1;
            }
        }
        for(i=n; i<=t; i++) {
            if(dp[i]<dp[i-n]+1)
                dp[i]=dp[i-n]+1;
        }

        int f = t;
        while(dp[f]<0) f--;

        if(f==t)
        printf("%d\n", dp[t]);
        else {
            printf("%d %d\n", dp[f],t-f);
        }

    }
    return 0;
}

题目大意:求将n写成若干个正整数的立方和的方法数
这道题的启发是组合问题可以转化为硬币问题

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
using namespace std;
int cube[25];
long long dp[10005];

int main() {
    int n, i,j;

    for(i=1; i<=21; i++) cube[i]=i*i*i;
    memset(dp, 0, sizeof(dp));
    dp[0]=1;
    for(i=1; i<=21; i++)
        for(j=cube[i]; j<10000; j++)
            dp[j] += dp[j-cube[i]];
    while(scanf("%d",&n)!=EOF) printf("%lld\n",dp[n]);
    return 0;
}

制造回文

题意:给出一个字符串,可以添加删除字符,或替换字符,求把它变成回文的最少操作次数。

递推

#include <cstdio>  
#include <cstring>  

#define min(a, b) ((a) < (b) ? (a) : (b))  

const int MAXN = 1010;  

int dp[MAXN][MAXN];  
char ch[MAXN];  
int t, cas;  

int main() {

    int i, j, k;
    scanf("%d", &t);
    for(cas=1; cas<=t; cas++) {
        scanf("%s", ch);
        int len=strlen(ch);
        for(i=0; i<len; i++) dp[i][i]=0;
        for(i=len-1; i>=0; i--)
            for(j=i+1; j<len; j++)
                if(ch[i]==ch[j]) 
                    dp[i][j]=dp[i+1][j-1];
                else 
                    dp[i][j]=min(dp[i+1][j],min(dp[i+1][j-1],dp[i][j-1]))+1;

        printf("Case %d: %d\n", cas, dp[0][len - 1]);  
    }  
    return 0;  
}  

uva10453
给一个字符串,要求添加最少个字符,把它变成回文串,并输出。
记忆化搜索

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
int vis[1005][1005];
char str[1005];
const int inf = 0x7fffffff;

int dp(int x, int y) {
    if(vis[x][y] != inf) return vis[x][y];
    if(x>=y) {
        vis[x][y]=0;
        return vis[x][y];
    }
    if(str[x]==str[y]) vis[x][y] = dp(x+1, y-1);
    else vis[x][y] = min(dp(x+1, y), dp(x,y-1))+1;
    return vis[x][y];
}

void output(int l, int r) {
    if(l>r) return;
    if(l==r) {
        putchar(str[l]);
        return;
    }
    if(str[l]==str[r]){
        putchar(str[l]);
        output(l+1,r-1);
        putchar(str[r]);
    }
    else if(vis[l][r] == vis[l+1][r]+1) {
        putchar(str[l]);
        output(l+1,r);
        putchar(str[l]);
    }
    else {
        putchar(str[r]);
        output(l,r-1);
        putchar(str[r]);
    }
}

int main() {
    int n, i, j;
    while(scanf("%s", str+1) != EOF) {
        int len = strlen(str+1);
        for(i=1; i<=len; i++)
            for(j=1; j<=len; j++)
                vis[i][j]=inf;
        int a = dp(1,len);
        printf("%d", a);
        output(1,len);
        printf("\n");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值