• DAG上的动态规划


    思想

    有些问题可以抽象为一个有向无环图,再进行求最短路,最长路或者路径计数问题,求得原问题的解.

    模型

    矩形嵌套问题:

    描述有n个矩形,每个矩形可以用a,b来描述,表示长和宽。矩形X(a,b)可以嵌套在矩形Y(c,d)中当且仅当a<c,b<d或者b<c,a<d(相当于旋转X90度)。例如(1,5)可以嵌套在(6,2)内,但不能嵌套在(3,4)中。你的任务是选出尽可能多的矩形排成一行,使得除最后一个外,每一个矩形都可以嵌套在下一个矩形内。

    输入:
    第一行是一个正正数N(0<N<10),表示测试数据组数,
    每组测试数据的第一行是一个正正数n,表示该组测试数据中含有矩形的个数(n<=1000)
    随后的n行,每行有两个数a,b(0<a,b<100),表示矩形的长和宽

    输出:
    每组测试数据都输出一个数,表示最多符合条件的矩形数目,每组输出占一行

    样例输入:
    1
    10
    1 2
    2 4
    5 8
    6 10
    7 9
    3 1
    5 8
    12 10
    9 7
    2 2

    样例输出:
    5

    分析

    将矩形之间的可嵌套关系抽象成二元关系,矩形x可以嵌套 在矩形y里面,就从x到y连一条有向边,且这个有向图是无环的.这样这个问题便转化为了求图上的长路径.

    代码(只考虑单组输入)
    #include <bits/stdc++.h>
    
    using namespace std;
    
    #define ll long long
    
    struct Mat{
        int x;
        int y;
    }mat[100]; // 矩形结构体,大小视具体题目规模而定
    
    int n, x, y;
    int a[100][100], dis[100]; // a数组为邻接矩阵,dis数组存储路径长度
    
    int DP(int i) { // 这里用记忆化搜索来做,参数传起点
        if(dis[i] > 0) // 大于0说明已经确定了路径,直接返回,节省时间
            return dis[i];
        dis[i] = 1; // 别忘了这里的赋值
        for(int j = 0; j < n; j++) {
            if(a[i][j] == 1) // 两点有i到j的边
                dis[i] = max(dis[i], DP(j)+1); // 取当前值和到i前的一个点加1的最大值
        }
    
        return dis[i];
    }
    
    void Print(int i) { // 字典序输出
        cout << i << " ";
        for(int j = 1; j <= n; j++)
            if(a[i][j] && dis[i] == dis[j]+1) {
                Print(j);
                break;
            }
    }
    
    int main() {
        
        cin >> n;
        for(int i = 0; i < n; i++) {
            cin >> mat[i].x >> mat[i].y;
        }
    
        // 建图
        for (int i = 0; i < n; i++) {
            for(int j = i+1; j < n; j++) {
                if((mat[i].x < mat[j].x && mat[i].y < mat[j].y) || (mat[i].x < mat[j].y && mat[i].y < mat[j].x))
                    a[i][j] = 1;
    
                if((mat[j].x < mat[i].x && mat[j].y < mat[i].y) || (mat[j].x < mat[i].y && mat[j].y < mat[i].x))
                    a[j][i] = 1;            
            }
        }
        int Max = 0;
        // 没有确定起点终点,就挨着找呗
        for (int i = 1; i <= n; i++) {
            if(DP(Max) < DP(i))
                Max = i; 
        }
        cout << dis[Max] << endl;
        Print(Max);
        return 0;
    }
    

    最少硬币问题

    Description
    设有n种不同面值的硬币,各硬币的面值存于数组T[1:n]中。现要用这些面值的硬币来找钱。可以使用的各种面值的硬币个数存于数组Coins[1:n]中。 对任意钱数0≤m≤20001,设计一个用最少硬币找钱m的方法。 对于给定的1≤n≤10,硬币面值数组T和可以使用的各种面值的硬币个数数组Coins,以及钱数m,0≤m≤20001,计算找钱m的最少硬币数。

    Input
    输入数据第一行中只有1个整数给出n的值,第2行起每行2个数,分别是 T[j]和Coins[j]。最后1行是要找的钱数m。

    Output
    输出数据只有一个整数,表示计算出的最少硬币数。问题无解时输出-1。

    Sample Input
    3
    1 3
    2 3
    5 3
    18
    Sample Output
    5

    分析

    紫书上说把这个问题抽象成从s(all)点到0点的有向图,但是直接按问题的原本描述来做也不难理解.

    代码
    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int INF = 1000000;
    int n, all; // n硬币种类数,all需要换的总钱数
    
    // dp[i]表示i元钱时的所需的最少硬币数,v数组存储面值,t数组存储对应面值的钱数目
    int dp[20005], v[20], t[20005]; 
    
    void Print(int i) { // 还是字典序输出
        for(int j = 1; j <= n; j++) {
            if(i >= v[j] && dp[i-v[j]] + 1 == dp[i]) {
                cout << v[j] << " ";
                Print(i-v[j]);
                break;
            }      
        }
    }
    
    int main() {
        cin >> n;
        for(int i = 1; i <= n; i++) {
            cin >> v[i];
            cin >> t[i];
        }  
        cin >> all;
        for(int i = 1; i <= 20005; i++) {
            dp[i] = INF; // 初值都赋为最大
        }  
        dp[0] = 0; // 重要的事情说三遍重要的事情说三遍重要的事情说三遍
        for(int i = 1; i <= n; i++) // 遍历每一种面值的硬币
            for(int j = 1; j <= t[i]; j++) // 遍历个数
                for(int k = all; k >= v[i]; k--)
                    dp[k] = min(dp[k], dp[k-v[i]]+1);
    
        if(dp[all] == INF) // 没找到方案
            cout << -1;
        else
            cout << dp[all];
        cout << endl;
        Print(all); // 光解题不用写这个
        return 0;
    }
    
  • 相关阅读:
    在Ubuntu下使用命令删除目录
    Visual Studio添加lib到链接依赖项的几种方法
    svn回到某个历史版本的做法
    iOS菜鸟成长笔记(3)——斯坦福公开课学习(1)
    VS自定义开发向导中的vsdir文件的简单说明
    OpenGL编程逐步深入(十一)组合变换
    iOS菜鸟成长笔记(2)——网易彩票练习
    AngularJs轻松入门(九)与服务器交互
    AngularJs轻松入门(八)Cookies读写
    AngularJs轻松入门(七)多视图切换
  • 原文地址:https://www.cnblogs.com/knightoflake/p/14510345.html
Copyright © 2020-2023  润新知