DAG上动态规划

很多动态规划问题都可以转化为DAG上的最长路,最短路,或路径计数问题。

硬币问题:

有N中硬币,面值分别为v1,v2,v3,……vn,每种都无穷多,给定非负整数S,可以选用多少个硬币,使他们的总和恰好为S。输出硬币数目的最小值和最大值。

解:每种面值看作一个点,表示:还需要凑足的面值。则开始状态为S,目标状态为0;若当前状态为i,当使用硬币j后,状态转为i-v[j].

代码说明好了。

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <algorithm>
 5 #include <cmath>
 6 #include <string>
 7 #include <vector>
 8 #include <set>
 9 #include <map>
10 #include <queue>
11 #include <stack>
12 using namespace std;
13 const int INF = 0x7fffffff;
14 const double EXP = 1e-8;
15 const int MS = 100005;
16 int minv[MS], maxv[MS];
17 int V[MS];
18 int main()
19 {
20     fill(minv, minv + MS, INF);
21     fill(maxv, maxv + MS, -INF);
22     minv[0] = maxv[0] = 0;
23     int N, S;
24     cin >> N >> S;
25     for (int i = 1; i <= N; i++)
26         cin >> V[i];
27 
28     for (int i = 1; i <= S; i++)
29         for (int j = 1; j <=N;j++)
30             if (i >= V[j])
31             {
32                 minv[i] = min(minv[i], minv[i - V[j]] + 1);
33                 maxv[i] = max(maxv[i], maxv[i - V[j]] + 1);
34             }
35     cout << minv[S] << " " << maxv[S] << endl;
36     return 0;
37 }

 输出字典序最小的方案:

 1 void print_ans(int *d, int s)
 2     {
 3         for (int i = 1; i <= n;i++)
 4             if (s >= v[i] && d[s] == d[s - v[i]] + 1)
 5             {
 6                 cout << i << " ";
 7                 print_ans(d, s - v[i]);
 8                 break;
 9             }
10     }
11     print_ans(minv, S);
12     cout << endl;
13     print_ans(maxv, S);

另一种方法求字典序最小的方案。

用min_coin[i]来记录状态为i时,最小的j,满足d[j]+1=d[i];

code:

 1 for (int i = 1; i <= S; i++)
 2         for (int j = 1; j <= n; j++)
 3         {
 4             if (i > =v[j])
 5             {
 6                 if (minv[i] > minv[i - v[j]] + 1)  //如果j从大到小   则>=
 7                 {
 8                     minv[i] = minv[i - v[j]] + 1;
 9                     min_coin[i] = j;
10                 }
11                 if (maxv[i] < maxv[i - v[j]] + 1)  //如果j从大到小,则<=
12                 {
13                     maxv[i] = maxv[i - v[j]] + 1;
14                     max_coin[i] = j;
15                 }
16             }
17         }
18     void print_ans(int *d, int S)
19     {
20         while (S)
21         {
22             cout << d[S] << " ";
23             s -= v[d[s]];
24         }
25     }
26     print_ans(min_coin, S);
27     cout << endl;
28     print_ans(max_coin, S);

问题二:矩形嵌套。

矩形的长宽为a,b;设一个矩形的长宽为a,b,另一个矩形长宽为c,d;当a<c&&b<d  或 b<c&&a<d时,两个矩形可以嵌套。

现在给出N个矩形,嵌套组合为第一个嵌套在第二个,第二个可以嵌套在第三个,第三个可以嵌套在第四个,……。

求数量多的嵌套组合的个数。

解:DAG 法。每个矩形抽象为一个点,当矩形a可以嵌套在矩形b时,我们在a,b之间连一条边。那么问题转化为求DAG上的最长路径。

设d[i]为从节点i出发的最长路径,则有d[i]=max(d[j]+1),   (i,j)是一条边。

 1 //记忆化搜索
 2     int d[MS];
 3     int dp(int i)
 4     {
 5         int &ans = d[i];
 6         if (ans > 0)
 7             return ans;
 8         ans = 1;
 9         for (int j = 1; j <= n; j++)
10             if (G[i][j])
11                 ans = max(ans, dp(j)+ 1);
12         return ans;
13     }

 输出字典序最小的方案:

 1 void print_ans(int i)   //  d[i]==max  &&  i is smallest
 2     {
 3         cout << i << " ";
 4         for (int j = 1; j <= n; j++)
 5         {
 6             if (G[i][j] && d[i] == d[j] + 1)
 7             {
 8                 print_ans(j);
 9                 break;
10             }
11         }
12     }
原文地址:https://www.cnblogs.com/767355675hutaishi/p/4268643.html