zoukankan      html  css  js  c++  java
  • dp_c_区间dp_g

    You Are The One

    题意:有n个人准备按顺序上台,上台前有个小黑屋(先进后出,即栈),可以被安排进去等待,也可以直接上台,一个人一旦被安排进去,后面的人就可以先上台(小黑屋无限大)。每个人有一个愤怒值angry,如果他是第k个上台的,那么他的怒气和就是(k-1)*angry,如何运用小黑屋安排上台顺序,使得这n个人的怒气总和最小。

    思路:这是一道区间dp题(这几天做的都是区间dp的专题,不是就见鬼了),状态很容易想到,用f[l][r],表示l~rr-l+1个人的最小怒气总和,那么如何转移,其实区间dp大概的套路也就是三个for( for len ; for l,r ; for k (l,r) ),而这题其中的小细节也比较容易观察到,那么问题来了,这也是为什么这题区间dp拿来写的原因,一般的k枚举都是lr来划分区间,但是这题显然没有什么用,为什么没用?因为要用小黑屋啊!如果直接枚举断点,而这题的l, r点是没什么关系的(如果真的有,个人感觉也很难推出,因为每个人的出场顺序其实是不一定的),怎么算出最优?(不用小黑屋也可以看做进去马上出来,问题不大),那么考虑到一个问题,既然出场顺序不一定,那k就枚举出场顺序吧,显然区间l~r的一个人出场顺序就只有r-l+1种,因为如果只有len个人,怎么做到第len+1出场?(一场跑步比赛,你超越了最后一名,你是第几!),至此,转移的for考虑完了,那么这个k来枚举谁呢?第一个?最后一个?随便一个?其实,随便一个都可以,因为最后区间最优,其实顺序是定的,所以先枚举谁都可以,那么就枚举这个区间的第一个吧,因为好写。可以想想,第一个人第k个出场,是不是1~1+k-1比他早出场,1+k~end在他后面出场(所以这些人都多等待了k个人,因而怒气值要多加上k次),那么转移式子就出来了。
    题外:写这题的契机其实有二,一是昨天听同学说了一题区间dp挺有意思的去补了一发,发现自己竟然写出来了dp,说出来是有点开心的,虽然那题并不复杂。而之前一直很害怕dp,觉得能想出来转移的(当然是要能A的正解,瞎想的没有意义)简直就是神仙。最近真的认真思考了一些dp题,发现从简入繁,其实dp挺有意思的,而且个人感觉做dp最大的收益就是很容易看懂别人写的代码(不只是dp的),因为其实dp挺锻炼思维的,一旦开始思考了,理解能力肯定不断地提升。二就是觉得这题区间dp的for套路有意思,k是来枚举顺序的,和别的区间dp(目前自己做到的)不太一样,就放了上来。


    Codes:

    #include <bits/stdc++.h>
    #define pb push_back
    #define de(x) cout << #x << " = " << x << endl
    #define clr(a,b) memset(a,b,sizeof(a))
    using namespace std;
    
    typedef long long ll;
    const int INF = 0x3f3f3f3f;
    const int N = 110;
    
    int f[N][N];
    int a[N], pr[N];
    
    int main()
    {
    	int n;
    	int cas = 1;
    	int t;
    	scanf("%d", &t);
    	while ( t -- )
    	{
    		scanf("%d", &n);
    		pr[0] = 0;
    		for ( int i = 1; i <= n; i ++ )
    		{
    			scanf("%d", &a[i]);
    			pr[i] = pr[i-1] + a[i];
    			f[i][i] = 0;
    		}
    		
    		for ( int len = 2; len <= n; len ++ )
    		{
    			for ( int l = 1, r; ( r = l + len - 1 ) <= n; l ++ )
    			{
    				f[l][r] = INF;
    				for ( int k = 1; k <= len; k ++ )
    				{
    					f[l][r] = min( f[l][r], (k-1)*a[l] + f[l+1][l+k-1] + f[l+k][r] + k*(pr[r]-pr[l+k-1]) );
    				}
    			}
    		}
    		printf("Case #%d: %d
    ", cas++, f[1][n]);	
    	}
    	return 0;
    }
    
  • 相关阅读:
    线段树 建树 单点修改 单点/区间查询
    JAVAEE学期总结
    Spring框架教程IDEA版-----更新中
    第一章操作系统引论-------批处理、分时、实时各个操作系统特点 进程与线程的区别
    读《阿法狗围棋系统的简要分析》
    matlab启动后的默认路径
    从长辈们的故事谈起
    在成为一名老司机的路上不要狂奔
    物理学与其它科学的关系
    读《现象级带货网红的自我修养》
  • 原文地址:https://www.cnblogs.com/FormerAutumn/p/9819741.html
Copyright © 2011-2022 走看看