题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=4283
题目大意:给定一个序列,序列内的人有屌丝值Di,是第然后将这个序列进栈,第i个人如果是第k个出栈,那么最后的屌丝总值增加Di * (k-1), 求一个出栈序列使得总屌丝值最小。
解题思路:2012年天津网赛的1006题,当时不会做,因为一直想着记录栈里面的状态,也就是谁谁谁在栈里面,然后就想着用bitset什么的,然后就没有然后了.
言归正传,本题的模型是求一个合法的出栈序列使得屌丝总值最小,需要用区间DP解决之。合法的出栈序列中有一个很重要的性质:[1,n]这是一开始的所有元素,当1第k个出栈时[2,k]肯定比1先出栈,[k+1,n]肯定比1后出栈,正因为只要1才能第k个出栈.。这样一个区间划分成两个子区间[2,k],[k+1,n],就这样递归下去只到区间长度为1.而区间DP正是解决这类区间划分问题的利器,其实区间DP也就是一种思想啦。
区间DP一般有两种写法,三个for循环或者记忆化搜索,个人觉得记忆化搜索虽然效率低点,但是写起来相当优美。
本题我写的for循环写法是参照cxlove的写法,把当前选择对后续选择的影响给提前计算了,这也是区间Dp经常用到的技巧。而记忆化搜索写法在搜索的过程中都只考虑当前的影响,因为多了一维,所以可以这样,如果开两维,就必须将后续影响考虑在内了.
测试数据:
InPut:
10
5
1 2 3 4 5
5
5 4 3 2 2
100
41 67 34 0 69 24 78 58 62 64 5 45 81 27 61 91 95 42 27 36 91 4 2 53 92 82 21 16
18 95 47 26 71 38 69 12 67 99 35 94 3 11 22 33 73 64 41 11 53 68 47 44 62 57 37
59 23 41 29 78 16 35 90 42 88 6 40 42 64 48 46 5 90 29 70 50 6 1 93 48 29 23 84
54 56 40 66 76 31 8 44 39 26 23 37 38 18 82 29 41
OutPut:
Case #1: 20
Case #2: 24
Case #3: 170975
C艹代码:
#include <stdio.h> #include <string.h> #define MAX 104 #define INF (1<<29) #define min(a,b) ((a)<(b)?(a):(b)) int n,ans,arr[MAX]; int dp[MAX][MAX][MAX]; int Solve_DP(int s,int e,int k) { //s是区间头,e是区间尾,k表示第i个人第k个出栈 if (s == e) return arr[s] * (k - 1); if (s > e) return 0; if (dp[s][e][k] != INF) return dp[s][e][k]; int i,first,second; int cur,thisk,nextk; for (i = s; i <= e; ++i) { //区间分成两部分,[s,i]和[i+1,e] nextk = k + (i - s) + 1; //[i+1,e]区间内第i个人第nextk个出栈 thisk = k + (i - s); //[s,i]区间的第s个人第thisk个出栈 first = Solve_DP(s+1,i,k); //第1个区间经过分配得到最少屌丝值 second = Solve_DP(i+1,e,nextk); //第2个区间经过分配得到的最少屌丝值 cur = arr[s] * (thisk - 1); //第s个人第thisk出栈增加的屌丝值 dp[s][e][k] = min(dp[s][e][k],first+second+cur); } return dp[s][e][k]; } int main() { int i,j,k,t,cas = 0; scanf("%d",&t); while (t--) { scanf("%d",&n); for (i = 1; i <= n; ++i) scanf("%d",&arr[i]); for (i = 1; i <= n; ++i) for (j = 1; j <= n; ++j) for (k = 0; k < n; ++k) dp[i][j][k] = INF; ans = Solve_DP(1,n,1); printf("Case #%d: %d\n",++cas,ans); } }
#include <stdio.h> #include <string.h> #define MAX 104 #define INF (1<<29) #define min(a,b) ((a)<(b)?(a):(b)) int n,ans,arr[MAX]; int dp[MAX][MAX],sum[MAX]; int Solve_DP() { int i,j,k,len,end,tp; for (len = 1; len < n; ++len) //枚举区间长度 for (i = 1; i + len <= n; ++i) { //枚举区间头 end = i + len; //i为区间头,end为区间尾 for (k = i ; k <= end; ++k) { //第k个出栈 tp = arr[i] * (k-i); //因为i-1以前的可以不考虑,所以把第i个人当做是第一个出栈的人 tp += dp[i+1][k] + dp[k+1][end]; //把区间分成[i+1,k]和[k+1][en] tp += (k - i + 1) * (sum[end] - sum[k]);//重要的性质,如果第i个人第k个出栈,那么后面的人出栈顺序都大于k dp[i][end] = min(dp[i][end],tp); } } return dp[1][n]; } int main() { int i,j,k,t,cas = 0; scanf("%d",&t); while (t--) { scanf("%d",&n); for (i = 1; i <= n; ++i) { scanf("%d",&arr[i]); sum[i] = sum[i-1] + arr[i]; } memset(dp,0,sizeof(dp)); for (i = 1; i <= n; ++i) for (j = i + 1; j <= n; ++j) dp[i][j] = INF; ans = Solve_DP(); printf("Case #%d: %d\n",++cas,ans); } }