Hdu 4283 You Are the One (DP_区间DP)

题目链接: 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 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);
	}
}

本文ZeroClock原创,但可以转载,因为我们是兄弟。

你可能感兴趣的:(Hdu 4283 You Are the One (DP_区间DP))