• DAG模型


    数字三角形:

    1、递归计算

    int solve(int i,int j) {
        return a[i][j] +(i==n?0:max(solve(i+1,j),solve(i+1,j+1)));
    }

    2、记忆化搜索,不用指明计算顺序,并且保证每个状态只计算一次

    int solve(int i,int j) {
        if(d[i][j]>=0) return d[i][j];
        return d[i][j] = a[i][j] +(i==n?0:max(solve(i+1,j),solve(i+1,j+1)));
    }

    3、递推计算

    for(int j=1;j<=n;j++) 
        d[n][j] = a[n][j];
    
    for(int i=n-1;i>=1;i--) {
        for(int j=1;j<=i;j++) {
            d[i][j] = a[i][j] + max(d[i+1][j],d[i+1][j+1]);
        }
    }

    刘汝佳紫书P262

    嵌套矩形问题:

    典型的二元关系,用图来建模,要是x可以嵌套在y里面,就x->y连线,这个图是有向无环图,有向可以理解,无环的意思就是说,一个矩形不可能直接或者间接的把自己嵌套起来。

    问题: 求最多嵌套多少? 

    就是在这个图上求一个最长路

    同数字三角形那样,d(i)表示从节点I出发的最长路的长度

    那么d(i) = max{(d[j]+1)|(i,j)ŒE};最后遍历一遍d(i);

    #include <bits/stdc++.h>
    using namespace std;
    #define maxn 10000
    
    int G[maxn][maxn];
    int dp[maxn];
    int n;  //n个节点
    
    //DAG
    int DAG(int i) {
    
        int &ans = dp[i];
        if(ans>0)
            return ans;
        ans = 1;
        for(int j=1;j<=n;j++) {
            if(G[i][j])
                ans = max(ans,DAG(j)+1);
        }
        return ans;
    }
    
    void print_ans(int i) {
    
        printf("%d ",i);
        for(int i=1;i<=n;i++) {
            if(G[i][j]&&d[i]==d[j]+1) {
                print_ans(j);
                break;
            }
        }
    }
    
    int main()
    {
    
        memset(dp,0,sizeof(dp));
        //建图
    
        for(int i=1;i<=n;i++)
            DAG(i);
    
        int ans = -1;
        int f = -1;
        for(int i=1;i<=n;i++)
        {
            if(ans<dp[i]) {
                ans = dp[i];
                f = i;
            }
        }
        print_ans(i);
    
        return 0;
    }

    硬币问题:(固定了终点,状态起点是S,状态终点是0)

    最长路,最短路。

    代码编译不能过,很多函数写同名了,主要记录思想。

    以 i 出发的最长路:d(i) = max(d(j)+1|(i,j)<E)

    以 i 结束的最长路:d(i) = max(d(j)+1|(j,i)<E)

    推荐方案二。

    对于方案一,代码细节难度有一点高。

    第一: S = 0,本身是可以的,所以dp[N]初始化-1;然后遇到没有计算的,ans = 0;是错误的,原因:节点S不一定到达0状态,但是返回值却是0,而0却可以是别的含义,就是S = 0,不用拿硬币。所以ans = -INF;

    用vis数组会方便很多,思路清晰,保证每个状态只访问一次。而且不用担心特殊值之间的冲突了。

    然后,求最大,最小两个值,记忆化搜索要写两个。

    这时可以递推。注意递推顺序。得到minv,maxv数组,这是可以像记忆化搜索那样搜索路径。

    minv[S] ==minv[S-v[j]] + 1;
    printf("%d ",j);

    但是有更好的方案:

    就是找到局部最优方案时记录路径。

    min_coin[i] = j;
    max_coin[i] = j;

    然后递归打印,这样就不用循环遍历了

    void print_ans(int *min_coin,int S) {
      while(S) {
        printf("%d ",min_coin[S]);
        S-=v[min_coin[S]];
      }
    }

    下面是全部代码:

    //固定终点的最长路和最短路
    
    
    #include <bits/stdc++.h>
    
    using namespace std;
    
    #define N 10000
    #define INF 0x3f3f3f3f
    
    const int v[] = {0,1,2,5,10,20,50,100};
    int n;  //硬币总数
    int S;  //总钱数
    
    int dp[N];
    bool vis[N];
    
    int dp(int S) {
    
        int & ans = dp[S];
        if(ans!=-1) return ans;
    
        ans = -(1<<30);
        for(int i=1;i<=n;i++) {
            if(S>=v[i])
                ans = max(ans,dp(S-v[i])+1);
        }
        return ans;
    }
    
    //dp方案二,利用vis数组标记状态
    int dp(int S) {
    
        if(vis[S]) return dp[S];
        vis[S] = 1;
    
        int & ans = dp[S];
        ans = -(1<<30);
        for(int i=1;i<=n;i++) {
    
            if(S>=v[i])
                ans = max(ans,dp(S-v[i])+1);
        }
    }
    
    void print_ans(int *d,int S) {
    
        for(int i=1;i<=n;i++) {
    
            if(S>=v[i]&&d[S]==d[S-v[i]]+1) {
                printf("%d ",i);
                print_ans(d,S-v[i]);
                break;
            }
    
        }
    
    }
    
    //改进的print_ans()
    void print_ans(int &min_coin,int S) {
    
            while(S) {
                printf("%d ",min_coin[S]);
                S-=v[min_coin[S]];
            }
    
    }
    
    
    int main()
    {
        scanf("%d",&S);
        memset(vis,0,sizeof(vis));
        memset(dp,-1,sizeof(dp));
    
        int ans = dp(S);
        if(ans!=(1<<30))
            printf("OK");
    
    
        int minv[N] = {0};
        int maxv[N] = {0};
    
        minv[0] = maxv[0] = 0;
        for(int i= 1;i<=S;i++) {
            minv[i] = INF;
            maxv[i] = -INF;
        }
    
        for(int i=1;i<=S;i++) {
            for(int j=1;j<=n;j++) {
                if(i>=v[j]) {
                    minv[i] = min(minv[i],minv[i-v[j]]+1);
                    maxv[i] = max(maxv[i],maxv[i-v[j]]+1);
                }
            }
        }
    
        printf("%d %d
    ",minv[S],maxv[S]);
    
        print_ans(minv,S);
        print_ans(maxv,S);
    
    
        //空间换时间
    
        int min_coin[N];
        int max_coin[N];
    
        for(int i=1;i<=S;i++) {
            for(int j=1;j<=n;j++) {
    
                if(i>=v[j]) {
                    if(minv[i]>minv[i-v[j]]+1) {
                        minv[i] = minv[i-v[j]]+1;
                        min_coin[i] = j;
                    }
                    if(maxv[i]<maxv[i-v[j]]+1) {
                        maxv[i] = maxv[i-v[j]] + 1;
                        max_coin[i] = j;
                    }
    
                }
    
            }
    
        }
    
        return 0;
    }
  • 相关阅读:
    2020年终总结
    vi编辑器使用基本操作
    为什么Windows下重载Nginx配置不生效
    JAVA、C#中使用正则表达式替换字符串
    领域事件、集成事件、事件总线区别与关系
    asp.net core中使用Serilog以及自定义Enricher
    C语言----文件(高阶篇二十八)
    C语言----指针与一维数组(高阶篇二十七)
    C语言----位运算(进阶篇二十六)
    C语言----指针基础(进阶篇二十五)
  • 原文地址:https://www.cnblogs.com/TreeDream/p/5974124.html
Copyright © 2020-2023  润新知