DAG 上的动态规划

https://blog.csdn.net/txl199106/article/details/49639925
暂存~

DAG 上的动态规划(训练指南—大白书)


有向无环图(DAG,Directed Acyclic Graph)上的动态规划是学习动态规划的基础。很多问题都可以转化为DAG上的最长路、最短路或路径计数问题。


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

解题思路:
       如何求DAG中不固定起点的最长路径呢?仿照数字三角形的做法,设d(i)表示从节点i出发的最长路长度,应该如何写状态方程呢?第一步只能走到它的相邻点,因此:
           d(i) = max { d(j) + 1 | (i,j)-> E }
其中E为边集,最终答案为d(i).那如果要求输出字典序最小的最长路径呢?那么必须找到第一个最长的路径的值然后递归输出。

代码:

  1. #include<stdio.h>  
  2. #include<stdlib.h>  
  3. #include<string.h>  
  4. #include<algorithm>  
  5. #include<iostream>  
  6. using namespace std ;  
  7. const int MX = 1000 + 10 ;  
  8. int n ;  
  9. int G[MX][MX],dp[MX] ;  
  10. struct node  
  11. {  
  12.     int x,y ;  
  13. }T[MX] ;  
  14. void buildGraph() // 建图  
  15. {  
  16.     memset(G,0,sizeof(G)) ;  
  17.     for(int i=0 ;i<n ;i++)  
  18.       for(int j=0 ;j<n ;j++)  
  19.         if(T[i].x>T[j].x&&T[i].y>T[j].y)  
  20.              G[i][j]=1 ;  
  21. }  
  22. int DAG(int x) // 记忆化求解  
  23. {  
  24.     int& ans = dp[x] ;  
  25.     if(ans > 0) return ans ;  
  26.     ans=1 ;  
  27.     for(int i=0 ;i<n ;i++)  
  28.       if(G[x][i])  
  29.       {  
  30.           int mx=DAG(i)+1 ;  
  31.           ans = ans > mx ? ans : mx ;  
  32.       }  
  33.     return ans ;  
  34. }  
  35. void print(int x) // 打印路径  
  36. {  
  37.     printf("%d ",x) ;  
  38.     for(int i=0 ;i<n ;i++)  
  39.       if(G[x][i]&&dp[x]==dp[i]+1)  
  40.       {  
  41.           print(i) ;  
  42.           break ;  
  43.       }  
  44. }  
  45. int main()  
  46. {  
  47.      int Tx ;  
  48.      scanf("%d",&Tx) ;  
  49.      while(Tx--)  
  50.      {  
  51.          scanf("%d",&n) ;  
  52.          for(int i=0 ;i<n ;i++)  
  53.          {  
  54.              scanf("%d%d",&T[i].x,&T[i].y) ;  
  55.              if(T[i].x>T[i].y)  
  56.                  swap(T[i].x,T[i].y) ;  
  57.          }  
  58.          int ans=1 ;  
  59.          buildGraph() ;  
  60.          memset(dp,-1,sizeof(dp)) ;  
  61.          for(int i=0 ;i<n ;i++)  
  62.          {  
  63.              int mx=DAG(i) ;  
  64.              ans= mx > ans ? mx : ans ;  
  65.          }  
  66.          for(int i=0 ;i<n ;i++)// 寻找第一个点  
  67.           if(dp[i]==ans)  
  68.           {  
  69.               printf("%d\n",ans) ;  
  70.               print(i) ;  
  71.              break ;  
  72.           }  
  73.      }  
  74.      return 0 ;  
  75. }  

二、硬币问题

题目描述:
        有n种硬币,面值分别为V1,V2...,Vn,每种都有无限多。给定非负整数S,可以选用多少个硬币,使得面值之和恰好为S?输出硬币数目的最小值和最大值。0 <= n <= 100, 0 <= S <= 10000, 1 <= Vi <= S。
解题思路:
        本题的本质还是DAG上的路径问题。我们把每种面值看作一个点,表示"还需要凑足的面值",则初始状态为S,目标状态为0。若当前的状态i,每使用一个硬币j,状态便转移到i-Vj。这个模型和嵌套矩形一题类似,但也有些明显的不同之处:上题并没有确定路径的起点和终点(可以把任意矩形放在第一个和最后一个),而本题的起点必须是S,终点必须是0。把终点固定之后"最短路"才是有意义的。在嵌套矩形中,最短序列显然是空(如果不允许空的话,就是单个矩形,不管怎样都是平凡的),而本题的最短路径却不是那么容易确定的。             
       接下来考虑"硬币问题"。注意到最长路和最短路的求法是类似的,下面只考虑最长路。由于终点固定,d(i)的确切含义变为"从节点i出发到节点0的最长路径长度"。
代码:

  1. #include<stdio.h>  
  2. #include<stdlib.h>  
  3. #include<iostream>  
  4. #define INF 1<<30  
  5. #define maxn 100+10  
  6. using namespace std ;  
  7. int V[maxn],n;  
  8. int min[maxn],max[maxn];  
  9.   
  10. inline int Min(int a,int b){return a<b?a:b;}  
  11. inline int Max(int a,int b){return a>b?a:b;}  
  12.   
  13. //打印可行的方案   
  14. void print_ans(int* d,int S)  
  15. {  
  16.   for(int i=1;i<=n;i++)  
  17.   {  
  18.     if(S>=V[i] && d[S]==d[S-V[i]]+1)  
  19.     {  
  20.        printf("%d ",V[i]);  
  21.        print_ans(d,S-V[i]);  
  22.        break;  
  23.     }  
  24.   }  
  25. }  
  26. int main()  
  27. {  
  28.   int S;  
  29.   while(~scanf("%d%d",&S,&n)) //输入面值S和面值的种数n   
  30.   {       
  31.     for(int i=1;i<=n;i++)  
  32.       scanf("%d",&V[i]);  
  33.       max[0]=0; min[0]=0;  
  34.     for(int i=1;i<=S;i++)  
  35.       min[i]=INF,max[i]=-INF;  
  36.     //递推实现   
  37.     for(int i=1;i<=S;i++)  
  38.       for(int j=1;j<=n;j++)  
  39.         if(i>=V[j])  
  40.         {  
  41.           min[i]=Min(min[i],min[i-V[j]]+1);  
  42.           max[i]=Max(max[i],max[i-V[j]]+1);  
  43.         }  
  44.      print_ans(min,S);    
  45.      printf("    min\n");  
  46.      print_ans(max,S);    
  47.      printf("    max\n");  
  48.      printf("min:%d max:%d\n",min[S],max[S]);     
  49.   }  
  50.   return 0;  
  51. }  

分析:本质上市一个DAG上的路径问题,我们把每种面值看做一个点,表示还需凑足的面值,则初始状态为0,目标状态为0,若当前在i,则每使用一枚硬币j,状态转移到i-vj。
代码:

  1. #include<stdio.h>  
  2. #define N 1100  
  3. int v[N],min[N],max[N],min_coins[N],max_coins[N];  
  4.   
  5. void print_ans(int *d,int s, int n) {  
  6.     while(s){  
  7.         printf("%d ",v[d[s]]);  
  8.         s-=v[d[s]];  
  9.     }  
  10.     printf("\n");  
  11. }  
  12. int main() {  
  13.     int T,i,j,n,s;  
  14.     scanf("%d",&T);  
  15.     while(T--) {  
  16.         scanf("%d %d",&n,&s);  
  17.         for(i=0;i<n;i++)  
  18.             scanf("%d",&v[i]);  
  19.         min[0]=max[0]=0;  
  20.         for(i=1;i<=s;i++){  
  21.             min[i]=0x7FFFFFFF;  
  22.             max[i]=-0x7FFFFFFF;  
  23.         }  
  24.         for(i=1;i<=s;i++){  
  25.             for(j=0;j<n;j++){  
  26.                 if(i>=v[j]){  
  27.                     //min[i]=min[i]<(min[i-v[j]]+1)?min[i]:(min[i-v[j]]+1);  
  28.                     if(min[i]>min[i-v[j]]+1){  
  29.                         min[i]=min[i-v[j]]+1;  
  30.                         min_coins[i]=j;  
  31.                     }  
  32.                     if(max[i]<max[i-v[j]]+1){  
  33.                         max[i]=max[i-v[j]]+1;  
  34.                         max_coins[i]=j;  
  35.                     }  
  36.                     //max[i]=max[i]>(max[i-v[j]]+1)?max[i]:(max[i-v[j]]+1);  
  37.                 }  
  38.             }  
  39.         }  
  40.         printf("%d %d\n",min[s],max[s]);  
  41.         print_ans(min_coins,s,n);  
  42.         print_ans(max_coins,s,n);  
  43.     }  
  44.     return 0;  
  45. }  

上面的代码中,如果不需要输出路径的话,则可以不要max_coins和min_coins数组,下面在写一个递归的。

  1. #include<stdio.h>  
  2. #include<string.h>  
  3. #define N 1100  
  4.   
  5. int v[N],d[N],vis[N];  
  6.   
  7. int dp(int s, int n) {  
  8.     int i;  
  9.     if(vis[s])   
  10.         return d[s];  
  11.     vis[s]=1;  
  12.       
  13.     d[s]=-1<<30;//没有算过的我们假定很小   
  14.     for(i=0;i<n;i++)  
  15.         if(s>=v[i]&&d[s]<dp(s-v[i],n)+1)  
  16.             d[s]=dp(s-v[i],n)+1;  
  17.     return d[s];  
  18. }  
  19. void print_ans(int s,int n){  
  20.     int i;  
  21.     for(i=0;i<n;i++){  
  22.         if(s>=v[i]&&d[s]==d[s-v[i]]+1){  
  23.             printf("%d ",v[i]);//输出所选的银币面值   
  24.             print_ans(s-v[i],n);  
  25.             break;  
  26.         }  
  27.     }  
  28. }  
  29. int main() {  
  30.     int T,i,n,s,ans;  
  31.     scanf("%d",&T);  
  32.     while(T--) {  
  33.         scanf("%d %d",&n,&s);  
  34.         for(i=0;i<n;i++) {  
  35.             scanf("%d",&v[i]);  
  36.         }   
  37.         memset(vis,0,sizeof(vis));  
  38.         vis[0]=1;  
  39.         d[0]=0;//终点状态药初始化为0,访问过   
  40.         ans=dp(s,n);  
  41.         printf("%d\n",ans);  
  42.         print_ans(s,n);   
  43.         printf("\n");  
  44.     }  
  45.     return 0;  
  46. }  

只写了求最长路径的,如果要求最短路径,则和最长路径类似,在此省略。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值