You Are the One
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 3032 Accepted Submission(s): 1352
Problem Description
The TV shows such as You Are the One has been very popular. In order to meet the need of boys who are still single, TJUT hold the show itself. The show is hold in the Small hall, so it attract a lot of boys and girls. Now there are n boys enrolling in. At
the beginning, the n boys stand in a row and go to the stage one by one. However, the director suddenly knows that very boy has a value of diaosi D, if the boy is k-th one go to the stage, the unhappiness of him will be (k-1)*D, because he has to wait for
(k-1) people. Luckily, there is a dark room in the Small hall, so the director can put the boy into the dark room temporarily and let the boys behind his go to stage before him. For the dark room is very narrow, the boy who first get into dark room has to
leave last. The director wants to change the order of boys by the dark room, so the summary of unhappiness will be least. Can you help him?
Input
The first line contains a single integer T, the number of test cases. For each case, the first line is n (0 < n <= 100)
The next n line are n integer D1-Dn means the value of diaosi of boys (0 <= Di <= 100)
Output
For each test case, output the least summary of unhappiness .
Sample Input
2 5 1 2 3 4 5 5 5 4 3 2 2
看了半个上午网上的题解才基本上明白了......边界处理的好微妙...dp大法好
题意说的是1 - n个屌丝男,然后通过一个小黑屋也就是个栈可以改变他们的顺序,每个人有个屌丝值,然后如果这个人排第k个,则他的不高兴值为(k - 1) * D
问所有人的不高兴值之和最小是多少
先分析这个问题,对于区间1 - n,第一个人可能是第一个上场,也可能是第n个上场,其实对于第一个人,他有可能是1 - n中第k个上场(1 <= k <= n)
为什么他可以是第1 - n中任意一个上场呢,因为
比如说这样
原始队伍:1 2 3 4 5
栈底-->2 3 4 5<--栈顶 舞台 :1
1直接上舞台,剩下的四个可以直接上,也可以先进小黑屋再上,这时候1就是第一个上了
栈底-->1 2 3 4 5<--栈顶 舞台:null
栈底-->1 2 3<--栈顶 舞台:4 5
比如这两种情况,1就是最后上场的
还有在1 - n之间的
队伍:3 4 5
栈底-->1 2<--栈顶 舞台:null
然后这时先让1 2出栈上舞台
队伍:3 4 5
栈底--><--栈顶 舞台:2 1
然后3 4 5再上
栈底--><--栈顶 舞台:2 1 3 4 5
这时候1就是排第二了,其他位置也可以相似地得到
发现了子问题
因为1可以是第1 - n中任意一个上舞台,不妨设他是第k个上的,然后发现在考虑这个问题的时候考虑哪种情况都是可以分成三部分
标号为1的屌丝本身,上舞台之后排在在1之前的k - 1个,在1之后的n - k个
然后再看在1之前上舞台的k - 1个屌丝,他们可以以同样的分析方法进行更小范围的独立分析,在1之后的n - k个也可以
1在第k个上舞台时,他的不高兴值为a[i] * (k - 1),那么只要知道他之前的所有屌丝的不高兴值之和的最小值sumpre,和他之后的所有屌丝的不高兴值之和的最小值之和sumnext,最后1 - n上的最小值就是
a[i] * (k - 1) + sumpre + sumnext
sumpre和sumnext又可以以同样的方式递归分析下去
直到精确到不可再分的区间长度为一的个人
定义函数int solve(i, j)函数返回区间[i, j]上的题目要求的最小值,那么
solve(i, j) = min(solve(i + 1, i + k - 1) + solve(i + k, j) + a[i] * (k - 1) + (sum[j] - sum[i + k - 1]) * k) (1 <= k <= j - i + 1)
区间[i, j]
i i + 1, i + 2, i + 3, ..........,i + k - 1 i + k ..........i + k + 1.........j
第k个上 之前的k - 1个 之后的 j - i - k + 1个
因为在第i之后的那个区间[i + k, j]在分析的时候是内部独立考虑的,因为在考虑这个子问题的时候肯定不知道它外面的父问题是什么
所以在[i + k, j]上的最优解只是独立考虑[i + k, j]的最小值,在综合到[i, j]这个区间时由于[i, i + k - 1]这k个都在他们前面所以
[i + k, j]这个最优解还要再加上D(i + k) * k + D(i + k + 1) * k + ...... + D(j) * k, D(x)为第x个屌丝的屌丝值
如果用前缀和来表示的话就是前j个的屌丝值和sum[j],前i + k - 1个的屌丝值和sum[i + k - 1]
D(i + k) * k + D(i + k + 1) * k + ...... + D(j) * k = (sum[j] - sum[i + k - 1]) * k
边界
发现在k = 1时
solve(i + 1, i) + solve(i + 1, j) + a[i] * (1 - 1) + (sum[j] - sum[i]) * 1
这个时候也就是第i个是排在[i, j]这个区间的第一个,可见他之前没有别人,从i + 1到j全在他后面
而且有个没有意义的solve(i + 1, i)明显要让这个值有意义只有让它等于0
solve(i + 1, j) + (sum[j] - sum[i])
在k = j - i + 1时
solve(i + 1, j) + solve(j + 1, j) + a[i] * (j - i) + (sum[j] - sum[j]) * (j - i + 1);
这个时候i排在[i, j]这个区间最后一个,从i + 1到j全在他前面
这时候又出现了solve(j + 1, j)没有意义,也是明显他要等于0,而且因为都在i前面,所以[i + 1, j]上的所有屌丝都不用补(sum[j] - sum[i + k - 1]) * k) 了,当然为0
solve(i + 1, j) + a[i] * (j - i)
实现
记忆化搜索,由于系统调用栈,相对慢一些
//记忆化搜索 #include <iostream> #include <cstring> #include <algorithm> using namespace std; const int maxn = 100 + 10, INF = 0x3f3f3f3f; int n, a[maxn], sum[maxn], dp[maxn][maxn]; int solve(int i, int j) { if (i > j) return 0; int ans = dp[i][j]; if (ans != -1) return ans; ans = INF; for (int k = 1; k <= j - i + 1; k++) { ans = min(ans, solve(i + 1, i + k - 1) + solve(i + k, j) + a[i] * (k - 1) + (sum[j] - sum[i + k - 1]) * k); } return dp[i][j] = ans; } int main() { int T; scanf("%d", &T); for (int t = 1; t <= T; t++) { scanf("%d", &n); sum[0] = 0; for (int i = 1; i <= n; i++) { scanf("%d", &a[i]); sum[i] = sum[i - 1] + a[i]; } memset(dp, -1, sizeof(dp)); printf("Case #%d: %d\n", t, solve(1, n)); } return 0; }
开数组直接递推,很快
形象来看像是这么个过程,就是父问题去覆盖子问题的过程,每次的覆盖范围都在扩大,最终扩大到n,将所有的子问题的
解都覆盖了,也就是所有子问题向上构成了最终问题的解,dp的奇妙之处啊啊啊啊啊啊啊啊啊。。。。
//递推 #include <iostream> #include <cstring> #include <algorithm> using namespace std; const int maxn = 100 + 10, INF = 0x3f3f3f3f; int n, a[maxn], sum[maxn], dp[maxn][maxn]; int main() { int T; scanf("%d", &T); for (int t = 1; t <= T; t++) { scanf("%d", &n); sum[0] = 0; for (int i = 1; i <= n; i++) { scanf("%d", &a[i]); sum[i] = sum[i - 1] + a[i]; } for (int i = 0; i <= n; i++) { for (int j = 0; j <= n; j++) { if (i > j) dp[i][j] = 0; else dp[i][j] = INF; } } /*l为计算的区间长度 要先有子问题的最优解才能向上推出更大区间的最优解 所以先是区间长度为1的最优解,然后为2。。。一直到整个区间[1, n] */ for (int l = 1; l <= n; l++) { for (int i = 1; i <= n; i++) { int j = i + l - 1; if (j > n) continue; //防越界 for (int k = 1; k <= j - i + 1; k++) { dp[i][j] = min(dp[i][j], dp[i + 1][i + k - 1] + dp[i + k][j] + (k - 1) * a[i] + (sum[j] - sum[i + k - 1]) * k); } } } printf("Case #%d: %d\n", t, dp[1][n]); } return 0; }