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;
}
posted @ 2021-03-10 11:07  湖上的程序员  阅读(116)  评论(0编辑  收藏  举报