玲珑杯 1074 - Pick Up Coins (选硬币 || 区间DP)

1 篇文章 0 订阅


DESCRIPTION

There are n coins in a line, indexed from 0 to n-1. Each coin has its own value.You are asked to pick up all the coins to get maximum value. If the you pick coini (1 ≤ i ≤n-2), you will get value[left]×value[i]×value[right]value[left]×value[i]×value[right] coin value. Here left and right are adjacent indices of i. After the picked, the left and right then becomes adjacent.

Note.

If you pick the first coin, you can assume the value[left] is 1.

If you pick the last coin, you can assume the value[right] is 1.

Find the maximum value when you pick up all the coins.

INPUT
The first line is a single integer TT, indicating the number of test cases.
For each test case:
The first line contains a number nn (3≤ nn ≤10 3) — The number of magic coins.
The second line contains nn numbers ( all numbers ≤10) — The value of each coin.
OUTPUT
For each test case, return a number indicates the maximum value after you picked up all the coins.
SAMPLE INPUT
133 5 8
SAMPLE OUTPUT
152
HINT
Hint In the sample, answer = 120 + 24 + 8 = 152 [3,5,8] --> [3,8] --> [8]



题意:给你一串序列,让你每次挑选一个数字,从序列中删除该数字,并记录该数字与相邻数字的积,最后将这些数相加,使得值要最大。


解题:区间DP... 入门题,  这题决策的是挑选数字的顺序,我们开个DP【L】【R】 维护该区间内全部被删除的最大值。

转移方程为 DP【L】【R】=DP 【L】【K-1】+DP【k+1】【R】+C【L-1】*C【k】*C【R+1】 ,K表示区间【L】【R】内,最后一个被删除的元素,这句话好好理解,

是解题的关键!!!!!!!    ,由于K是最后被删除的,保证了DP 【L】【K-1】,DP【k+1】【R】 这两个状态是合法的,K将序列合法的分为两块,这两块是互不干扰的

纸上多画画,多模拟模拟几遍就能出来了。。 区间DP太神奇了


突然觉得区间DP,就是最暴力的暴力题,枚举每种状态,通过小的状态转移到大的状态!!!!




#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = 1e3+100;
const int inf = 0x3f3f3f3f;
int dp[N][N],a[N],vis[N][N];
int DP(int l,int r)
{
	if(vis[l][r] || l>r) return dp[l][r];
	vis[l][r]=1;
	for(int k=l;k<=r;k++) dp[l][r]=max(dp[l][r],DP(l,k-1)+DP(k+1,r)+a[l-1]*a[k]*a[r+1]);
	return dp[l][r];
}
int main()
{
	ios::sync_with_stdio(false);
	int T,i,j,n;
	cin>>T;
	while(T--) {
		cin>>n;
		memset(dp,0,sizeof(dp));
		memset(vis,0,sizeof(vis));
		for(i=1;i<=n;i++) cin>>a[i];
		a[0]=1,a[n+1]=1;          //控制临界值 
		DP(1,n);
		cout<<dp[1][n]<<endl;
	}
	return 0;
}


区间dp顾名思义就是在一个区间上进行的一系列动态规划。对一些经典的区间dp总结在这里。


1) 石子归并问题


题目链接:http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=737


描述: 有N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆。求出总的代价最小值。


分析:要求n个石子归并,我们根据dp的思想划分成子问题,先求出每两个合并的最小代价,然后每三个的最小代价,依次知道n个。

定义状态dp [ i ] [ j ]为从第i个石子到第j个石子的合并最小代价。

那么dp [ i ] [ j ] = min(dp [ i ] [ k ] + dp [ k+1 ] [ j ]) 

那么我们就可以从小到大依次枚举让石子合并,直到所有的石子都合并。

这个问题可以用到平行四边形优化,用一个s【i】【j】=k 表示区间 i---j 从k点分开才是最优的,这样的话我们就可以优化掉一层复杂度,变为O(n^2).

代码:

[cpp]   view plain  copy  print ? 在CODE上查看代码片 派生到我的代码片
  1. #include <cstdio>  
  2. #include <cstring>  
  3. #include <algorithm>  
  4. #define N 210  
  5. int dp[N][N],sum[N];  
  6. int main()  
  7. {  
  8.     int n;  
  9.     while(~scanf("%d",&n))  
  10.     {  
  11.         int a[N];sum[0]=0;  
  12.         for(int i=1;i<=n;i++){  
  13.             scanf("%d",&a[i]);  
  14.             sum[i]=sum[i-1]+a[i];  
  15.         }  
  16.         memset(dp,0,sizeof(dp));  
  17.         int i,j,l,k;  
  18.         for(l = 2; l <= n; ++l)  
  19.         {  
  20.             for(i = 1; i <= n - l + 1; ++i)  
  21.             {  
  22.                 j = i + l - 1;  
  23.                 dp[i][j] = 2100000000;  
  24.                 for(k = i; k < j; ++k)  
  25.                 {  
  26.                     dp[i][j] = std::min(dp[i][j],dp[i][k] + dp[k + 1][j] + sum[j] - sum[i-1]);  
  27.                 }  
  28.             }  
  29.         }  
  30.         printf("%d\n", dp[1][n]);  
  31.     }  
  32.     return 0;  
  33. }  

平行四边形优化代码:

[cpp]   view plain  copy  print ? 在CODE上查看代码片 派生到我的代码片
  1. #include <cstdio>  
  2. #include <cstring>  
  3. #include <algorithm>  
  4. #define N 210  
  5. int dp[N][N],sum[N],s[N][N];  
  6. int main()  
  7. {  
  8.     int n;  
  9.     while(~scanf("%d",&n))  
  10.     {  
  11.         int a[N];sum[0]=0;  
  12.         memset(s,0,sizeof(s));  
  13.         for(int i=1;i<=n;i++){  
  14.             scanf("%d",&a[i]);  
  15.             s[i][i]=i;  
  16.             sum[i]=sum[i-1]+a[i];  
  17.         }  
  18.         memset(dp,0,sizeof(dp));  
  19.         int i,j,l,k;  
  20.         for(l = 2; l <= n; ++l)  
  21.         {  
  22.             for(i = 1; i <= n - l + 1; ++i)  
  23.             {  
  24.                 j = i + l - 1;  
  25.                 dp[i][j] = 2100000000;  
  26.                 for(k = s[i][j-1]; k <= s[i+1][j]; ++k)  
  27.                 {  
  28.                     if(dp[i][j]>dp[i][k] + dp[k + 1][j] + sum[j] - sum[i-1])  
  29.                     {  
  30.                         dp[i][j]=dp[i][k] + dp[k + 1][j] + sum[j] - sum[i-1];  
  31.                         s[i][j]=k;  
  32.                     }  
  33.                 }  
  34.             }  
  35.         }  
  36.         printf("%d\n", dp[1][n]);  
  37.     }  
  38.     return 0;  
  39. }  



2)括号匹配


题目链接:

poj2955,http://poj.org/problem?id=2955

nyoj 15 http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=15


描述:给出一串的只有‘(’ ‘)’  '[‘  ']'四种括号组成的串,让你求解需要最少添加括号数让串中的所有括号完全匹配。


分析:我们求出这个串的最大匹配,然后串的总长度-最大匹配就是答案。


方法1:首先能想到的是转化成LCS(最长公共子序列),枚举中间点,求所有的LCS中的最大值 * 2就是最大匹配。但是复杂度较高,光LCS一次就O(n^2)的复杂度。


方法2:

首先考虑怎么样定义dp让它满足具有通过子结构来求解、

定义dp [ i ] [ j ] 为串中第 i 个到第 j 个括号的最大匹配数目

那么我们假如知道了 i 到 j 区间的最大匹配,那么i+1到 j+1区间的是不是就可以很简单的得到。

那么 假如第 i 个和第 j 个是一对匹配的括号那么dp [ i ] [ j ] = dp [ i+1 ] [ j-1 ] + 2 ;

那么我们只需要从小到大枚举所有 i 和 j 中间的括号数目,然后满足匹配就用上面式子dp,然后每次更新dp [ i ] [ j ]为最大值即可。

更新最大值的方法是枚举 i 和 j 的中间值,然后让 dp[ i ] [ j ] = max ( dp [ i ] [ j ] , dp [ i ] [ f ] + dp [ f+1 ] [ j ] ) ;


如果要求打印路径,即输出匹配后的括号。见http://blog.csdn.net/y990041769/article/details/24238547详细讲解

代码:

[cpp]   view plain  copy  print ? 在CODE上查看代码片 派生到我的代码片
  1. #include <iostream>  
  2. #include <cstring>  
  3. #include <algorithm>  
  4. #include <string>  
  5. using namespace std;  
  6. const int  N = 120;  
  7. int dp[N][N];  
  8. int main()  
  9. {  
  10.     string s;  
  11.     while(cin>>s)  
  12.     {  
  13.         if(s=="end")  
  14.             break;  
  15.         memset(dp,0,sizeof(dp));  
  16.         for(int i=1;i<s.size();i++)  
  17.         {  
  18.             for(int j=0,k=i;k<s.size();j++,k++)  
  19.             {  
  20.                 if(s[j]=='('&&s[k]==')' || s[j]=='['&&s[k]==']')  
  21.                     dp[j][k]=dp[j+1][k-1]+2;  
  22.                 for(int f=j;f<k;f++)  
  23.                     dp[j][k]=max(dp[j][k],dp[j][f]+dp[f+1][k]);  
  24.             }  
  25.         }  
  26.         cout<<dp[0][s.size()-1]<<endl;  
  27.     }  
  28.     return 0;  
  29. }  


3)整数划分问题


题目链接:nyoj746 http://acm.nyist.net/JudgeOnline/problem.php?pid=746


题目描述:给出两个整数 n , m ,要求在 n 中加入m - 1 个乘号,将n分成m段,求出这m段的最大乘积


分析:根据区间dp的思想,我们定义dp [ i ] [ j ]为从开始到 i 中加入 j 个乘号得到的最大值。

那么我们可以依次计算加入1----m-1个乘号的结果

而每次放入x个乘号的最大值只需枚举第x个乘号的放的位置即可

dp [ i ] [ j ]  = MAX (dp [ i ] [ j ] , dp [ k ] [ j-1 ] * a [ k+1 ] [ i ] ) ;


代码:

[cpp]   view plain  copy  print ? 在CODE上查看代码片 派生到我的代码片
  1. #include <cstdio>  
  2. #include <cstring>  
  3. #define MAX(a,b) a>b?a:b  
  4. long long a[20][20];  
  5. long long dp[25][25];  
  6. int main()  
  7. {  
  8.     int T,m;  
  9.     scanf("%d",&T);  
  10.     getchar();  
  11.     while(T--)  
  12.     {  
  13.         char s[22];  
  14.         scanf("%s",s+1);  
  15.         scanf("%d",&m);  
  16.         int l=strlen(s),ok=1;  
  17.         memset(a,0,sizeof(a));  
  18.         for(int i=1;i<l;i++)  
  19.         {  
  20.             if(s[i]=='0')  
  21.                 ok=0;  
  22.             for(int j=i;j<l;j++)  
  23.             {  
  24.                 a[i][j]=a[i][j-1]*10+(s[j]-'0');  
  25.             }  
  26.         }  
  27.         if(ok==0&&l-1==m||l-1<m)  
  28.         {  
  29.             printf("0\n");continue;  
  30.         }  
  31.         long long x,ans;  
  32.         memset(dp,0,sizeof(dp));  
  33.         for(int i=0;i<l;i++)  
  34.             dp[i][1]=a[1][i];  
  35.         ans=0;  
  36.         if(m==1)  
  37.             ans=dp[l-1][1];  
  38.         for(int j=2;j<=m;j++)  
  39.         {  
  40.             for(int i=j;i<l;i++)  
  41.             {  
  42.                 ans=a[i][i];  
  43.                 for(int k=1;k<i;k++)  
  44.                 {  
  45.                     dp[i][j]=MAX(dp[i][j],dp[k][j-1]*a[k+1][i]);  
  46.                 }  
  47.             }  
  48.         }  
  49.         printf("%lld\n",dp[l-1][m]);  
  50.     }  
  51.     return 0;  
  52. }  





  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值