poj 3016 K-Monotonic 左偏树 + 贪心 + dp

//poj 3016 K-Monotonic
//分析:与2005年集训队论文黄源河提到的题目类似,给定序列a,求一序列b,b不减,且sigma(abs(ai-bi))最小。
//思路:去除左偏树(大根堆)一半的节点(向上取整),让左偏树的根节点上存放中位数;每个左偏树的根节点表示一个等值区间
//在本题中,我们将一段区间 与 一颗左偏树等同;将求调整给定数列 vi 为不减序列的代价 与 求取数列 bi 等同

  1 #include"iostream"
  2 #include"cstdio"
  3 #include"cstring"
  4 using namespace std;
  5 const int maxn = 1010;
  6 int v[maxn],l[maxn],r[maxn],d[maxn];    //左偏树节点信息,Merge维护
  7 int f[maxn][12],c[maxn][maxn];  //dp,c为cost
  8 int N,K;    //输入
  9
 10 int tot,root[maxn],sum_del[maxn],num_del[maxn],sum_now[maxn],num_now[maxn]; //左偏树根节点信息,solve维护
 11
 12 int Merge(int x,int y)
 13 {
 14     if(!x)
 15         return y;   //分治终点
 16     if(!y)
 17         return x;   //分治终点
 18     if(v[x]<v[y])
 19         swap(x,y);
 20     r[x] = Merge(r[x],y);   //分治
 21     if(d[l[x]]<d[r[x]])
 22         swap(l[x],r[x]);    //回溯维护l,r
 23     d[x] = d[r[x]]+1;   //回溯维护d
 24     return x;
 25 }
 26
 27 int dis_sum(int tot)    //求一段区间(左偏树)上各项差绝对值的和
 28 {//从树上去除的节点都是v值大于或者等于中位数的,未去除的节点都是小于或者等于中位数的
 29     return sum_del[tot]-num_del[tot]*v[root[tot]]+num_now[tot]*v[root[tot]]-sum_now[tot];
 30 }
 31
 32 void solve()
 33 //solve能将给定的一个整数序列 v1 , v2 , … , vn,求一个不下降序列 b1≤b2≤…≤bn,使得数列 {vi} 和 {bi} 的各项之差的绝对值之和 |v1 - b1| + |v2 - b2| + … + |vn - bn| 最小。
 34 {
 35     //巧妙之处在于:数列 {bi} 中的元素是从数列 {vi} 中选取的;选取方法是: 当连续的 vi 不断的小于bi时,bi只能不断的取 这个连续区间 [vl...vr] 的中位数(用反证法容易证明这个结论),当 vi 上升到前一个下降区间的中位数以上的时候, bi 上升到与 vi 相同的值,这样我们就能通过贪心的方法得到一个最小各项差的绝对值之和
 36     int i,j,ans;
 37     for(i = 1; i<=N; ++i) {
 38         tot = ans = 0;
 39         for(j = i; j<=N; ++j) {
 40             root[++tot] = j;
 41             //合并区间(左偏树)的一般化情况:考虑到当不断调整最后一个区间(左偏树)的中位数的时候可能会出现小于前一个区间(左偏树)中位数的情况,这时候就需要合并两个都不小的区间(左偏树),所以我们不妨将插入情况一般化为合并情况,对于每个元素都处理成一个区间(左偏树),用区间(左偏树)的合并操作来代替插入操作
 42             num_now[tot] = 1;
 43             //由于我们将元素一般化为了一个区间,所以我们现在从区间的角度去重新理解这个做法:不断的加入一个新的区间,当新区间的中位数小于前一个区间的中位数时,就从后往前将最后一个区间和前一个区间做一趟合并操作,直到最后一个区间的中位数大于前一个区间;所求的 bi 就是各个区间的中位数(即左偏树的根节点对应的值)
 44             sum_now[tot] = v[j];
 45             //聪明的读者可能会提问:这样能不能保证合并后区间的中位数没有在合并之前就被去除掉?答案是肯定的,一种比较直观的理解是:作平面直角坐标系,从后一个区间中位数的点向上平移,一定是先遇到前一个区间的中位数的点的水平高度,之后继续向上平移才有可能遇到最后一个区间内的其他点,画个图就能很容易理解合并后区间的中位数对应的点一定不会高于倒数第二个区间的中位数对应的点的水平高度的
 46             num_del[tot] = sum_del[tot] = 0;
 47             l[j] = r[j] = d[j] = 0;
 48             while(tot>1&&v[root[tot-1]]>v[root[tot]]) {
 49                 //如果靠后的区间(左偏树)的中位数小于前一个区间(左偏树),则合并这两个左偏树
 50                 ans -= dis_sum(tot-1)+dis_sum(tot);
 51                 //各项差绝对值的和 回溯
 52                 root[tot-1] = Merge(root[tot-1],root[tot]);
 53                 num_now[tot-1] += num_now[tot];
 54                 sum_now[tot-1] += sum_now[tot];
 55                 num_del[tot-1] += num_del[tot];
 56                 sum_del[tot-1] += sum_del[tot];
 57                 --tot;
 58                 while(num_now[tot]>num_del[tot]+1) {
 59                     //保证合并后区间的中位数对应的节点位于合并后的左偏树的根位置
 60                     --num_now[tot];
 61                     sum_now[tot] -= v[root[tot]];
 62                     ++num_del[tot];
 63                     sum_del[tot] += v[root[tot]];
 64                     root[tot] = Merge(l[root[tot]],r[root[tot]]);
 65                 }
 66                 ans += dis_sum(tot);
 67                 //各项差绝对值的和 加上 合并操作完成之后最后一个区间的 各项差绝对值的和
 68             }
 69             c[i][j] = min(c[i][j],ans);
 70         }
 71     }
 72 }
 73
 74 int main()
 75 {
 76     int i,j,k;
 77     while(scanf("%d%d",&N,&K)&&(N||K)) {
 78         memset(f,0x3f,sizeof(f));
 79         memset(c,0x3f,sizeof(c));
 80         for(i = 1; i<=N; ++i) {
 81             scanf("%d",&v[i]);
 82             v[i] -= i;
 83             //v[i] 为递增序列(的代价)相当于 v[i]-i 为不下降序列(的代价)
 84         }
 85         solve();
 86         for(i = 1; i<=N; ++i) {
 87             v[i] = -(v[i]+(i<<1));
 88             //v[i] 为递减序列(的代价)相当于 -(v[i]+i) 为不下降序列(的代价)
 89         }
 90         solve();
 91         f[0][0] = 0;
 92         for(i = 1; i<=N; ++i) {
 93             for(j = 1; j<=K; ++j) {
 94                 for(k = j-1; k<i; ++k) {
 95                     //f[前缀区间][前缀区间上的单调数列的个数],c[区间左端点][区间右端点]:将一段区间维护成单调区间的最小代价
 96                     f[i][j] = min(f[i][j],f[k][j-1]+c[k+1][i]);
 97                 }
 98             }
 99         }
100         printf("%d\n",f[N][K]);
101     }
102     return 0;
103 }

//拓展:在本题目的基础上要求k个单调序列按照升序和降序交错排列

时间: 2024-12-16 03:07:32

poj 3016 K-Monotonic 左偏树 + 贪心 + dp的相关文章

poj 3666 Making the Grade &amp; zoj 3512 Financial Fraud 左偏树 or dp

//poj 3666 //分析:只是在2005年集训队论文黄源河提到的题目上略微有一点点变化 1 #include"iostream" 2 #include"cstdio" 3 using namespace std; 4 const int maxn = 2100; 5 int v[maxn],l[maxn],r[maxn],d[maxn]; //节点信息 6 int N; 7 int tot,root[maxn],num_now[maxn],num_del[ma

【左偏树+贪心】BZOJ1367-[Baltic2004]sequence

[题目大意] 给定一个序列t1,t2,...,tn ,求一个递增序列z1<z2<...<zn , 使得R=|t1−z1|+|t2−z2|+...+|tn−zn| 的值最小.本题中,我们只需要求出这个最小的R值. [思路] -这个比加延迟标记的左偏树调试得还久……WA到死…… 如果ti是递增的,我们只需要取zi=ti: 如果ti是递减的,我们只需要取ti的中位数. 所以我们将ti分割成若干个区间,维护每个区间的中位数.对于[L,R]的区间,我们存放[L,(L+R)/2]在堆中.具体如下操作

POJ3666-Making the Grade(左偏树 or DP)

左偏树 炒鸡棒的论文<左偏树的特点及其应用> 虽然题目要求比论文多了一个条件,但是……只需要求非递减就可以AC……数据好弱…… 虽然还没想明白为什么,但是应该觉得应该是这样——求非递减用大顶堆,非递增小顶堆…… 这题和bzoj1367题意差不多,但是那题求的是严格递增.(bzoj找不到那道题,可能是VIP或什么原因? 严格递增的方法就是每一个数字a[i]都要减去i,这样求得的b[i]也要再加i,保证了严格递增(为什么对我就不知道了 代码比较水,因为题目数据的问题,我的代码也就钻了空子,反正ac

POJ3016-K-Monotonic(左偏树+DP)

我觉得我要改一下签名了……怎么会有窝这么啰嗦的人呢? 做这题需要先学习左偏树<左偏树的特点及其应用> 然后做一下POJ3666,这题的简单版. 思路: 考虑一下维护中位数的过程原数组为A,找到的不降数列为B当对于A的前n个数已经找好了最优解B[1…n],可知此时A被分成很多块,并被一些大顶堆记录,假设第i块有num个数,那么第i个堆维护这一块的最小的(num+1)/2个数,堆顶即为中位数.假设已经处理好前7个数,被分为两块 ([a,b],c,d) ([h,e],f) (每一块按升序排列,[]中

左偏树

概要:左偏树是具有左偏性质的堆有序二叉树,它相比于优先队列,能够实现合并堆的功能. 先仪式型orzorzozr国家集训队论文https://wenku.baidu.com/view/515f76e90975f46527d3e1d5.html 左偏树的节点定义: 1 struct node { 2 int lc, rc, val, dis; 3 } LTree[maxn]; 左偏树的几个基本性质如下: 节点的键值小于等于它的左右子节点的键值 节点的左子节点的距离不小于右子节点的距离 节点的距离等于

[BZOJ1455] 罗马游戏|左偏树

1455: 罗马游戏 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 870  Solved: 347[Submit][Status][Discuss] Description 罗马皇帝很喜欢玩杀人游戏. 他的军队里面有n个人,每个人都是一个独立的团.最近举行了一次平面几何测试,每个人都得到了一个分数. 皇帝很喜欢平面几何,他对那些得分很低的人嗤之以鼻.他决定玩这样一个游戏. 它可以发两种命令: 1. Merger(i, j).把i所在的团和j所在的团

【左偏树】【APIO】Dispatching

2809: [Apio2012]dispatching Time Limit: 10 Sec Memory Limit: 128 MB Submit: 1932 Solved: 967 Description 在一个忍者的帮派里,一些忍者们被选中派遣给顾客,然后依据自己的工作获取报偿.在这个帮派里,有一名忍者被称之为 Master.除了 Master以外,每名忍者都有且仅有一个上级.为保密,同时增强忍者们的领导力,所有与他们工作相关的指令总是由上级发送给他的直接下属,而不允许通过其他的方式发送.

ZOJ 3512 Financial Fraud (左偏树)

题意:给定一个序列,求另一个不递减序列,使得Abs(bi - ai) 和最小. 析:首先是在每个相同的区间中,中位数是最优的,然后由于要合并,和维护中位数,所以我们选用左偏树来维护,当然也可以用划分树来做. 代码如下: #pragma comment(linker, "/STACK:1024000000,1024000000") #include <cstdio> #include <string> #include <cstdlib> #inclu

HDU 1512 并查集+左偏树

Monkey King Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 3105    Accepted Submission(s): 1330 Problem Description Once in a forest, there lived N aggressive monkeys. At the beginning, they e