zoj 3512 Financial Fraud 给定一个整数序列a1, a2, … , an,求一个不下降序列b1 ≤ b2 ≤ … ≤ bn,使得数列{ai}和{bi}的各项之差的绝对值之和 |

In every quarter of these years, the two programmers would receive a data sheet, which provided a series of profit records a1 a2 ... aN in that period. Due to the economic crisis, the awful records might scare investors away. Therefore, the programmers were asked to falsifying these records into b1 b2 ... bN. In order to deceive investors, any record must be no less than all the records before it (it means for any 1 ≤ i < j ≤ Nbi ≤ bj).

On the other hand, they defined a risk value for their illegal work: risk value = | a1 - b1 | + | a2 - b2 | + ... + | aN - bN | . For example, the original profit records are 300 400 200 100. If they choose 300 400 400 400 as the fake records, the risk value is 0 + 0 + 200 + 300 = 500. But if they choose 250 250 250 250 as the fake records, the risk value is 50 + 150 + 50 + 150 = 400. To avoid raising suspicion, they need to minimize the risk value.

Now we will give you some copies of the original profit records, you need to find out the minimal possible risk value.

Input

There are multiple test cases (no more than 20).

For each test case, the first line contains one integer N (1 ≤ N ≤ 50000), the next line contains N integers a1 a2 ... aN (-109 ≤ ai ≤ 109). The input will end with N = 0.

Output

For each test case, print a single line that contains minimal possible risk value.

Sample Input
4
300 400 200 100
0
Sample Output
400

//

http://blog.csdn.net/king821221/article/details/2068668

假设我们已经找到前k个数a[1], a[2], … , a[k] (k<n) 的最优解,得到m个区间组成的队列,对应的解为 (w[1],w[2],…,w[m]),现在要加入a[k+1],并求出前k+1个数的最优解。首先我们把a[k+1]作为一个新区间直接加入队尾,令w[m+1]=a[k+1],然后不断检查队尾两个区间的解w[m]w[m+1],如果w[m]>w[m+1],我们需要将最后两个区间合并,并找出新区间的最优解(也就是序列a中,下标在这个新区间内的各项的中位数)。重复这个合并过程,直至w[1]≤w[2]≤…≤w[m]时结束,然后继续处理下一个数。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
//给定一个整数序列a1, a2, … , an,求一个不下降序列b1 ≤ b2 ≤ … ≤ bn,使得数列
//{ai}和{bi}的各项之差的绝对值之和 |a1 - b1| + |a2 - b2| + … + |an - bn| 最小。
const int N=51000;
const int DEEP=20;//划分树最多层数
struct Node
{
    int l,r;
};
Node tree[N*4];//线段树
int data[N];//数据
int seg[DEEP][N];//划分树
int LessMid[DEEP][N];//表示在[L,R]内有几个数小于等于date[mid]
void buildtree(int root,int l,int r,int d)//节点,左右区间,层数
{
    tree[root].l=l,tree[root].r=r;
    if(l==r) return ;//叶子节点
    int mid=(l+r)>>1;
    int lsame=mid-l+1;//左面最多可放几个与data[mid]相同的数
    for(int i=l;i<=r;i++)//得出实际能放几个相同的数
    {
        if(seg[d][i]<data[mid]) lsame--;
    }
    int tl=l,tr=mid+1,same=0;//划分树 ,tl,tr表示数的左右子树的起点
    for(int i=l;i<=r;i++)
    {
        if(i==l) LessMid[d][i]=0;//表示在[L,R]内有几个数小于等于date[mid]
        else LessMid[d][i]=LessMid[d][i-1];
        if(seg[d][i]<data[mid]) LessMid[d][i]++,seg[d+1][tl++]=seg[d][i];//划分到左子树
        else if(seg[d][i]>data[mid]) seg[d+1][tr++]=seg[d][i];//划分到右子树
        else //相等情况
        {
            if(same<lsame) same++,LessMid[d][i]++,seg[d+1][tl++]=seg[d][i];//左子树未满
            else seg[d+1][tr++]=seg[d][i];
        }
    }
    buildtree(root<<1,l,mid,d+1);
    buildtree((root<<1)+1,mid+1,r,d+1);
}
//查询[l,r]中的第k小数
int Query(int root,int l,int r,int d,int cnt)//节点,要查询的区间,层数,第cnt小数
{
    if(l==r) return seg[d][l];
    int s;//表示在[l,r]中有几个小于等于data[mid]的个数
    int ss;//表示在[tree[root].l,l-1]中有几个小于等于data[mid]的个数
    if(l==tree[root].l) s=LessMid[d][r],ss=0;
    else s=LessMid[d][r]-LessMid[d][l-1],ss=LessMid[d][l-1];
    if(s>=cnt) return Query(root<<1,tree[root].l+ss,tree[root].l+ss+s-1,d+1,cnt);
    else
    {
        int mid=(tree[root].l+tree[root].r)>>1;
        int bb=l-tree[root].l-ss;//表示[tree[root].l,l-1]中有多少个分到右面
        int b=r-l+1-s;//表示[l,r]有多少个分到右面
        return Query((root<<1)+1,mid+bb+1,mid+bb+b,d+1,cnt-s);
    }
}
int a[N];//data
Node range[N];//分为几段
int mid[N];//每段中位数
int teg;
int _abs(int x)
{
    return x>0?x:-x;
}
int main()
{
    int n;
    while(scanf("%d",&n)==1&&n)
    {
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            data[i]=a[i];
            seg[1][i]=data[i];
        }
        sort(data+1,data+1+n);
        buildtree(1,1,n,1);
        teg=0;
        for(int i=1;i<=n;i++)
        {
            range[++teg].l=i,range[teg].r=i;mid[teg]=a[i];
            for(int j=teg;j>1;j--)
            {
                if(mid[j]<mid[j-1])
                {
                    int l=range[j-1].l,r=range[j].r;
                    range[--teg].l=l,range[teg].r=r;
                    int k=(r-l)/2+1;
                    int tmp=Query(1,l,r,1,k);
                    mid[teg]=tmp;
                }
                else break;
            }
        }
        long long  cnt=0;
        for(int i=1;i<=teg;i++)
        {
            for(int j=range[i].l;j<=range[i].r;j++)
            {
                cnt+=(long long)_abs(a[j]-mid[i]);
            }
        }
        cout<<cnt<<endl;
    }
    return 0;
}
/*
3
3 2 1
*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值