区间动态规划

1、矩阵连乘,tyvj 1198(最优矩阵连乘),关键是写出动态转移方程。用DP[i][j]表示矩阵Ai乘到Aj的最优解,P[]用来存储矩阵的行和列,M[i-1]表示矩阵i的行,M[i]表示矩阵i的列。

当i==j时,DP[i][i]=0;

当i<j时,可以假设他从k位置隔开。比方说从A3..A8。那么A1A2(A3A4A5A6A7A8)A9。我们知道若A矩阵为r1*c的矩阵,A2为c*r2的矩阵,得到的将是一个r1*r2的矩阵,且连乘次数为r1*c*r2。假设我从5位置隔开,就可以分为A1A2((A3A4A5)(A6A7A8))A9。那么这个时候的次数为:DP[3][5]+DP[6][8]+M[2]*M[5]*M[8](A3的行*A5的列*A8的列)。那么我只需要模举这其中的组合然后取最小值即可,这个时候的动态转移方程为:DP[i][j]=min(DP[i][k]+DP[k+1][j]+M[i-1]*M[k]*M[j])(i<=k<j)

问题是:你要知道DP[i][k]和DP[k+1][j]的值,你才可以和DP[i][j]做比较,才能求出DP[i][j]的值。

思想是先求出相邻组的连乘次数,如一开始算相邻两个相乘,如图:

那么每两个相乘的结果我们可以得到。同理继续求三个相邻的结果。

计算的话比方说要求A1A2A3,可以化为(A1)(A2A3)=DP[1][1]+DP[2][3]+M[0]*M[1]*M[3],依次类推结果为:DP[i][j]=DP[i][i]+DP[i+1][j]+M[i-1]*M[i]*M[j]。

只有这样做后我才可以求任意组合的次数,求出三个一组的结果后,我就可以求任意的四个矩阵连乘的最小值。如:A4A5A6A7,随便怎么组合,通过上面是计算都可以求出相应的结果。

现在讨论下范围:用r保存要求的每每相邻的组数(2<=r<=n)(从2开始是这里前提条件是i<j),i的范围为:(1<=i<=n-r+1,稍作推理就知道,当r=3时,即三个三个一组时i<=9-3+1=7),j的值固定j=r+i-1,具体见代码:

  1. #include<iostream>  
  2. #include<cstring>  
  3. #include<cstdio>  
  4. using namespace std;  
  5. const int MAX=110;  
  6. #define min(a,b) (a)<(b)?(a):(b)  
  7. #define CLR(arr,val) memset(arr,val,sizeof(arr))  
  8. int n,M[MAX],DP[MAX][MAX];  
  9. void Matrix()  
  10. {   for(int r=2;r<=n;r++)          
  11.         for(int i=1;i<=n-r+1;i++)    
  12.         {   int j=r+i-1;  
  13.             DP[i][j]=DP[i+1][j]+M[i-1]*M[i]*M[j];  
  14.             for(int k=i;k<j;k++)  
  15.                 DP[i][j]=min(DP[i][k]+DP[k+1][j]+M[i-1]*M[k]*M[j],DP[i][j]);  
  16.         }   
  17. }  
  18. int main()  
  19. {   scanf("%d",&n);  
  20.     for(int i=0;i<=n;i++)  
  21.         scanf("%d",&M[i]);   
  22.     CLR(DP,0);  
  23.     Matrix();  
  24.     cout<<DP[1][n]<<endl;       
  25.     return 0;  
  26. }  

路径输出:

  1. #include<iostream>     
  2. #include<cstring>     
  3. #include<cstdio>     
  4. using namespace std;    
  5. const int MAX=110;    
  6. #define CLR(arr,val) memset(arr,val,sizeof(arr))     
  7. int n,M[MAX],DP[MAX][MAX],s[MAX][MAX];    
  8. void Matrix()    
  9. {   for(int r=2;r<=n;r++)            
  10.         for(int i=1;i<=n-r+1;i++)      
  11.         {   int j=r+i-1;    
  12.             DP[i][j]=DP[i+1][j]+M[i-1]*M[i]*M[j];    
  13.             s[i][j]=i; //i位置断开      
  14.             for(int k=i;k<j;k++)    
  15.             {   int temp=DP[i][k]+DP[k+1][j]+M[i-1]*M[k]*M[j];    
  16.                 if(temp<DP[i][j])  
  17.                 {   s[i][j]=k;//k位置断开      
  18.                     DP[i][j]=temp;  
  19.                 }  
  20.             }    
  21.         }     
  22. }    
  23. void Find(int i,int j)    
  24. {   if(i==j)    
  25.     {   cout<<'A'<<i;     
  26.         return ;    
  27.     }    
  28.     if(i<s[i][j]) cout<<"(";     
  29.     Find(i,s[i][j]);    
  30.     if(i<s[i][j]) cout<<")";    
  31.     if(s[i][j]+1<j) cout<<"(";      
  32.     Find(s[i][j]+1,j);     
  33.     if(s[i][j]+1<j) cout<<")";     
  34. }    
  35. int main()    
  36. {   scanf("%d",&n);    
  37.     for(int i=0;i<=n;i++)    
  38.         scanf("%d",&M[i]);     
  39.     CLR(DP,0);    
  40.     Matrix();    
  41.     Find(1,n);    
  42.     cout<<endl<<DP[1][n]<<endl;         
  43.     return 0;    
  44. }    

2、NYOJ 15(括号匹配),令DP[i][j]表示字符串s的第i个字符到第j个字符需要添加的最少括号数。

①、当i==j时,只有一个符号,需再添加一个字符,所以DP[i][i]=1

②、当i<j时,若是s[i]与S[j]配对,那么DP[i][j]=DP[i+1][j-1];

若是不配对,那么从中任选一个字符作为分界点,i<=k<j,就有:DP[i][j]=min(DP[i][j],DP[i][k]+DP[k][j])。具体实现和上例一样:

  1. #include<iostream>  
  2. #include<string>  
  3. #include<cstring>  
  4. using namespace std;  
  5. const int MAX=1010;  
  6. #define min(a,b) (a)<(b)?(a):(b)  
  7. #define CLR(arr,val) memset(arr,val,sizeof(arr))  
  8. string s;  
  9. int n,DP[MAX][MAX];  
  10. bool pipei(char c1,char c2)  
  11. {   if(c1=='('&&c2==')'||c1=='['&&c2==']'return true;  
  12.     return false;    
  13. }  
  14. int main()  
  15. {   cin>>n;  
  16.     cin.get();  
  17.     while(n--)  
  18.     {   cin>>s;      
  19.         CLR(DP,0);  
  20.         int len=s.length();  
  21.         for(int i=1;i<=len;i++) DP[i][i]=1;  
  22.         for(int r=2;r<=len;r++)  
  23.             for(int i=1;i<=len-r+1;i++)  
  24.             {   int j=r+i-1;  
  25.                 DP[i][j]=MAX; //一定要赋值   
  26.                 if(pipei(s[i-1],s[j-1])) DP[i][j]=DP[i+1][j-1];  
  27.                 for(int k=i;k<j;k++)  
  28.                     DP[i][j]=min(DP[i][k]+DP[k+1][j],DP[i][j]);     
  29.             }   
  30.         cout<<DP[1][len]<<endl;        
  31.     }  
  32.     return 0;  
  33. }  

3、石子合并:Tyvj 1055(石子合并),和这个题目类似的是Tyvj 1062(合并傻子)

  1. #include<iostream>  
  2. #include<cstring>   
  3. using namespace std;  
  4. const int MAX=1010;  
  5. #define CLR(arr,val) memset(arr,val,sizeof(arr))  
  6. int n,a[MAX],sum[MAX][MAX],DP[MAX][MAX];  
  7. int Sand()  
  8. {   for(int r=2;r<=n;r++)  
  9.         for(int i=1;i<=n-r+1;i++)  
  10.         {   int j=i+r-1;  
  11.             DP[i][j]=DP[i+1][j]+sum[i][j];  
  12.             for(int k=i;k<j;k++)  
  13.                 DP[i][j]=min(DP[i][j],DP[i][k]+DP[k+1][j]+sum[i][j]);  
  14.         }  
  15.     return DP[1][n];   
  16. }   
  17. int main()  
  18. {   cin>>n;  
  19.     for(int i=1;i<=n;i++)  
  20.         cin>>a[i];  
  21.     CLR(DP,0);  
  22.     CLR(sum,0);  
  23.     for(int i=1;i<=n;i++)  
  24.         for(int j=i;j<=n;j++)  
  25.             sum[i][j]=sum[i][j-1]+a[j];          
  26.     cout<<Sand()<<endl;             
  27.     return 0;  
  28. }   

4、加分二叉树。Tyvj 1073(加分二叉树)

  1. #include<iostream>  
  2. #include<cstring>  
  3. using namespace std;  
  4. const int MAX=50;  
  5. #define CLR(arr,val) memset(arr,val,sizeof(arr))  
  6. long n,a[MAX],DP[MAX][MAX],s[MAX][MAX];  
  7. int flag=1;  
  8. long Max()  
  9. {   for(int r=2;r<=n;r++)  
  10.         for(int i=1;i<=n-r+1;i++)   
  11.         {   int j=i+r-1;  
  12.             DP[i][j]=DP[i+1][j]+a[i];  
  13.             s[i][j]=i;   
  14.             for(int k=i+1;k<j;k++)  
  15.             {   int temp=DP[i][k-1]*DP[k+1][j]+a[k];             
  16.                 if(temp>DP[i][j])  
  17.                 {   DP[i][j]=temp;  
  18.                     s[i][j]=k;  
  19.                 }   
  20.             }  
  21.         }      
  22.     return DP[1][n];      
  23. }  
  24. void Search(int L,int R)  
  25. {   if(L>R) return ;   
  26.     if(flag) flag=0;  
  27.     else cout<<" ";   
  28.     cout<<s[L][R];  
  29.     if(L==R) return ;  
  30.     Search(L,s[L][R]-1);    
  31.     Search(s[L][R]+1,R);  
  32. }  
  33. int main()  
  34. {   cin>>n;  
  35.     CLR(DP,0);  
  36.     for(int i=1;i<=n;i++)  
  37.     {   cin>>a[i];  
  38.         DP[i][i]=a[i];   
  39.     }  
  40.     cout<<Max()<<endl;  
  41.     for(int i=1;i<=n;i++)  
  42.         s[i][i]=i; //子树为空,本身就为树的根节点   
  43.     Search(1,n);  
  44.     cout<<endl;  
  45.     return 0;  
  46. }  

题5:牢房:

描述

        SB王国中有一个奇怪的监狱,这个监狱里一共有P间牢房,这些牢房一字排开,从左到右按1到P进行编号,第i间后面紧挨着第(i+1)间(最后一间除外)。现在有P名罪犯被关押在这P间牢房里。某一天,上级下发了一个释放名单,要求每天释放名单上的一个人。这可把监狱中的看守们吓得不轻,因为看守们知道,现在牢房中的P个人,可以相互之间传话。如果某个人离开了,那么原来和这个人能够传上话的人都会很气愤,导致他们那天会一直大吼大叫,搞得看守们很是头疼,但是如果给这些要发火的人吃上肉,他们就会安静。现在看守们想知道,如何安排释放的顺序,才能使得他们花费的肉钱最少。1<=p<=1000,1<=Q<=100

输入

第一行两个数P和Q,Q表示要释放名单上的人数;

第二行Q个数,表示释放哪些人

输出

仅一行,表示最少给多少人次送肉.

样例输入

20 3
3 6 14

样例输出

35

提示:(样例说明)

       先释放14号监狱中的罪犯,要给1到13号监狱和15到20号监狱中的19人送肉吃;再释放6号监狱中的罪犯,要给1到5号监狱和7到13号监狱中的12人送肉吃;最后释放3号监狱中的罪犯,要给1到2号监狱和4到5号监狱中的4人送肉吃。

分析:用DP[i][j]表示在[i,j]区间所有该被释放的人都被释放的最小支付代价。

若是释放k,那么代价为DP[i][k-1]+DP[k+1][j]+(j-i),所以动态转移方程为:DP[i][j]=min(DP[i][k-1]+DP[k+1][j]+(j-i)).

  1. #include<iostream>  
  2. #include<cstring>  
  3. #include<cstdio>  
  4. #include<algorithm>   
  5. using namespace std;  
  6. const int MAX=1010;  
  7. const int Inf=100000000;  
  8. int n,m,a[MAX],sum[MAX],DP[MAX][MAX];  
  9. int main()  
  10. {   for(int i=0;i<MAX;i++)   
  11.     {   fill(DP[i],DP[i]+MAX,Inf);  
  12.         DP[i][i]=0;   
  13.     }   
  14.     scanf("%d%d",&n,&m);  
  15.     for(int i=1;i<=m;i++)  
  16.         scanf("%d",&a[i]);  
  17.     sort(a+1,a+m+1);    
  18.     for(int i=1;i<=m;i++)  
  19.         sum[i]=a[i]-a[i-1]-1;  
  20.     sum[m+1]=n-a[m];  
  21.     for(int i=1;i<=m+1;i++)  
  22.         sum[i]=sum[i]+sum[i-1];  
  23.     for(int i=m+1;i>=1;i--)  
  24.         for(int j=i+1;j<=m+1;j++)  
  25.             for(int k=i;k<j;k++)  
  26.                 DP[i][j]=min(DP[i][j],DP[i][k]+DP[k+1][j]+sum[j]-sum[i-1]+j-i-1);            
  27.     printf("%d\n",DP[1][m+1]);  
  28.     return 0;  
  29. }  
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值