一种神奇的DP思想

  最近考了个叫做NOIP模拟的题。。。。。

  真是难以吐槽。NOIP怎么考到了自动机去了。。。。。

  当然,想我这种蒟蒻就只能膜拜机房里的各位大牛了。。。。

  不管怎么说,我们来看一下这道很厉害的DP。

  题目大意:

  给定 N 个数, 请你将他们分成两组, 并使得两组的代价和最少

  (每一组的代价定义为 ∑¦Hpi-1 - Hpi¦, pi-1 < pi

  我考场上只想到了 20 分的DP:令 f[i][j][k]  表示 考虑前 i 个 两组结尾分别为 j , k  时的最优值。

  考试后,大牛就给了我一个 50 分算法:f[i][j] 表示 第一列以 i 结尾, 第二列以 j 结尾的最优值。(强制令 i > j)

  那么很明显 f[i][j] -> f[i+1][i]  或 f[i+1][j]

  :-( 怎么会这样呢?

  第二天,大牛告诉我说他 AC 了, 一问才知道 他强制令 i - j <= 20 这样居然骗到了 95 分。。。(⊙o⊙)…

  

  没办法,自己怎么会有那么厉害的YY能力?

  只能看题解:

    所以说不愧是题解

    令 g[i] = f[i][i-1]

    则 g[i] = min(g[j] + sum[i-1]  - sum[j] + abs(H[i] - H[j-1])) (sum[i] = sum[i-1] + abs(Hi - Hi-1))

    那么我们对这个绝对值号进行分类讨论,那么就可以维护处最优解了。

    我们用两棵树状数组来维护这个值 (即 g[j] - sum[j] ± H[j-1])

    我们先将 H[i] 离散化, 那么对于 H[i] > H[j] 查找的范围为 树状数组A [1 , i)

                               否则为 树状数组B (i, n]

    代码就是这个样子的:

    

 1 #include<cstdlib>
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<cstring>
 5 #define maxn 500500
 6 using namespace std;
 7 long long n,h[maxn];
 8 long long sum[maxn];
 9 long long Bit1[maxn],Bit2[maxn];
10 long long f[maxn];
11 long long ans,a[maxn],tot;
12 int lowbit(int x)
13 {
14     return x & (-x);
15 }
16 void change(long long bit[],int pos,long long x)
17 {
18     while(pos <= n){
19         bit[pos] = min(bit[pos],x);
20         pos += lowbit(pos);
21     }
22 }
23 long long ask(long long bit[],int pos)
24 {
25     long long ret = 0x7fffffffff;
26     while(pos){
27         ret = min(ret,bit[pos]);
28         pos -= lowbit(pos);
29     }
30     return ret;
31 }
32 int main()
33 {
34     freopen("sprung.in","r",stdin);
35     freopen("sprung.out","w",stdout);
36     scanf("%I64d",&n);
37     memset(Bit1,0x3f,sizeof(Bit1));
38     memset(Bit2,0x3f,sizeof(Bit2));
39     f[0] = 0;
40     ans = 0x7ffffffff;
41     for(int i=1;i<=n;++i) scanf("%I64d",&h[i]),a[i] = h[i];
42     sort(a+1,a+n+1); tot = unique(a+1,a+n+1) - a;
43     for(int i=1;i<=n;++i) sum[i] = sum[i-1] + abs(h[i] - h[i-1]);
44     change(Bit1,1,0);
45     change(Bit2,tot,0);
46     for(int i=2;i<=n;++i){
47         int pos = lower_bound(a+1,a+tot,h[i]) - a + 1;
48         long long x1 = ask(Bit1,pos) + h[i] ;
49         long long x2 = ask(Bit2,tot - pos + 1) - h[i];
50         f[i] = min(x1,x2) + sum[i-1];
51         ans = min(ans,f[i] + sum[n] -sum[i]);
52         pos = lower_bound(a+1,a+tot,h[i-1]) - a + 1;
53         change(Bit1,pos,f[i] - sum[i] - h[i-1]);
54         change(Bit2,tot - pos + 1,f[i] - sum[i] + h[i-1]);
55     }
56     printf("%I64d",ans);
57     return 0;
58 }

    

时间: 2024-10-09 15:40:29

一种神奇的DP思想的相关文章

DP思想在斐波那契数列递归求解中的应用

斐波那契数列:1, 1, 2, 3, 5, 8, 13,...,即 f(n) = f(n-1) + f(n-2). 求第n个数的值. 方法一:迭代 public static int iterativeFibonacci(int n) { //简单迭代 int a = 1, b = 1; for(int i = 2; i < n; i ++) { int tmp = a + b; a = b; b = tmp; } return b; } 方法二:简单递归 public static long

到底什么是dp思想(内含大量经典例题,附带详细解析)

期末了,通过写博客的方式复习一下dp,把自己理解的dp思想通过样例全部说出来 说说我所理解的dp思想 dp一般用于解决多阶段决策问题,即每个阶段都要做一个决策,全部的决策是一个决策序列,要你求一个 最好的决策序列使得这个问题有最优解 将待求解的问题分为若干个相互联系的子问题,只在第一次遇到的时候求解,然后将这个子问题的答案保存 下来,下次又遇到的时候直接拿过来用即可 dp和分治的不同之处在于分治分解而成的子问题必须没有联系(有联系的话就包含大量重复的子问题,那 么这个问题就不适宜分治,虽然分治也

线程池的基本思想还是一种对象池的思想

线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理.当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源. 比如:一个应用要和网络打交道,有很多步骤需要访问网络,为了不阻塞主线程,每个步骤都创建个线程,在线程中和网络交互,用线程池就变的简单,线程池是对线程的一种封装,让线程用起来更加简便,只需要创一个线程池,把这些步骤像任务一样放进线程池,在程序销毁时只要调用线程

AutoLayout代码布局使用大全—一种全新的布局思想

相信ios8出来之后,不少的ios程序员为了屏幕的适配而烦恼.相信不少的人都知道有AutoLayout 这么个玩意可以做屏幕适配,事实上,AutoLayout不仅仅只是一个为了多屏幕适配的工具, 它真正的意义所在是给了程序员一种全新的布局思想. 本文主要依据真实项目实例从三个方向全方位讲解AutoLayout的使用大全. 一.AutoLayout布局原理和语法 二.约束冲突和AutoLayout动画处理 三.AutoLayout布局思想,约束链的控制. 本文讲解的内容和代码主要依赖于一个名为UI

hdu 3030 Increasing Speed Limits (离散化+树状数组+DP思想)

Increasing Speed Limits Time Limit: 2000/10000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 481    Accepted Submission(s): 245 Problem Description You were driving along a highway when you got caught by the road p

记忆化搜索(搜索+dp思想)

一:简介 (1)记忆化搜索 即 搜索+动态规划数组记录上一层计算结果,避免过多的重复计算 算法上依然是搜索的流程,但是搜索到的一些解用动态规划的那种思想和模式作一些保存:一般说来,动态规划总要遍历所有的状态,而搜索可以排除一些无效状态.更重要的是搜索还可以剪枝,可能剪去大量不必要的状态,因此在空间开销上往往比动态规划要低很多. 记忆化算法在求解的时候还是按着自顶向下的顺序,但是每求解一个状态,就将它的解保存下来,以后再次遇到这个状态的时候,就不必重新求解了. 这种方法综合了搜索和动态规划两方面的

JAVA注释的另一种神奇用法

每个JAVA程序员在写程序的时候一定都会用到注释,本篇博客不是讲怎么定义注释,而是说明注释神奇的一种写法. 1 /** 2 * 这是一个测试类 3 */ 4 public class Test { 5 /** 6 * 程序的入口 7 */ 8 public static void main(String[] args) { 9 new Test(); 10 } 11 } 以上是两个普通的多行注释,在IDEA的环境下,选中方法或者类名,按住Ctrl+Q(Eclipse开发环境下直接按住Ctrl然后

Codeforces Round #388 (Div. 2) 749E(巧妙的概率dp思想)

题目大意 给定一个1到n的排列,然后随机选取一个区间,让这个区间内的数随机改变顺序,问这样的一次操作后,该排列的逆序数的期望是多少 首先,一个随机的长度为len的排列的逆序数是(len)*(len-1)/4,这是显然的,因为每种排列倒序一遍就会得到一个新序列,逆序数是len*(len-1)/2 - x(x为原排列的逆序数) 所以我们只需要把所有n*(n-1)/2的区间每种情况都随机化一遍再求逆序对,然后把这个值求和,就可以得到答案了 但是如果用朴素做法,那么复杂度是n^2的 考虑dp[x]表示以

hdu 4521 小明序列(线段树,DP思想)

题意: ①首先定义S为一个有序序列,S={ A1 , A2 , A3 , ... , An },n为元素个数 : ②然后定义Sub为S中取出的一个子序列,Sub={ Ai1 , Ai2 , Ai3 , ... , Aim },m为元素个数 : ③其中Sub满足 Ai1 < Ai2 < Ai3 < ... < Aij-1 < Aij < Aij+1 < ... < Aim : ④同时Sub满足对于任意相连的两个Aij-1与Aij都有 ij - ij-1 >