【左偏树】 poj3016


        ps: 难道我天生程序就写得丑么=。=!有一个算法在我手上变慢了(⊙o⊙)…

  

       左偏树主要优点是支持堆合并,当然,它牺牲了树的平衡,牺牲了树的平衡使得左偏树仅仅对最值的操作比较方便,对其他值的操作往往要借助lazy标记。

       左偏树并不极力维护树的平衡,而是以树的左偏为代价,保证从根节点一直往右走到达“外节点”的路径长度不超过logn,这样各种操作仍然保证了logn的复杂度。


       先来一道基础题:2005年集训队论文黄源河提到的题目,给定序列a,求一序列b,b不减,且sigma(abs(ai-bi))最小。

       

# include <cstdlib>
# include <cstdio>
# include <cmath>
# include <cstring>

using namespace std;

const int maxn = 200000;
long long abs1(long long x) {return x<0?-x:x;};
void swap(int &x, int &y) {int tmp=x;x=y;y=tmp;};

int l[maxn], r[maxn], st[maxn], size[maxn], must[maxn], d[maxn];
long long a[maxn];
int n, tot;
long long ans;

int merge(int x, int y)
{
	if (!x) return y; if (!y) return x;
	if (a[x]<a[y]) swap(x,y);
	r[x]= merge(r[x],y);
	if (d[l[x]]<d[r[x]]) swap(l[x],r[x]);
	d[x]=d[r[x]]+1; return x;
}

void updata(int i)
{
	for (;size[i]>(must[i]+1>>1);)
		size[i]--, st[i]= merge(l[st[i]], r[st[i]]);
}
int main()
{
	int i,j,p;
	freopen("road.in", "r", stdin);
	freopen("road.out", "w", stdout);
	scanf("%d", &n);
	for (i = 1; i <= n; i++)
		scanf("%I64d", &a[i]);
	for (i = 1; i <= n; i++)
	{
		st[++tot] = i; size[tot] = 1; must[tot]=1;
		for (;tot>1&&a[st[tot]]<a[st[tot-1]];)
		   st[tot-1]=merge(st[tot], st[tot-1]),size[tot-1]+=size[tot], must[tot-1]+=must[tot], updata(--tot);  
	}
	for (i = 1,p=1; i <= tot; i++)
		for (j=1; j<= must[i]; j++,p++)
		  ans+= abs1(a[p]-a[st[i]]);
	printf("%I64d", ans);
	return 0;
}


POJ3016, 同样给定一个序列a, 求一序列b, 使sigma(abs(ai-bi))最小,且b必须能分成k段单调序列。

一个简单的dp f[ i, j ] = min(f[ i -1, k], + cost[k+1, j]) 状态的意义是a1~aj,分成i段,所需要的最小代价,其中cost[ i , j ]表示将ai~aj有序化的代价。

 那么问题转化为如何快速求cost数组,明显这就是上面那道题。

唯一要修改的是 上边求的是不减的,这里要求递增和递减的, 可以这样转化,要使b[ i ] < b[i + 1] 即 b[ i ] <= b[i +1]-1 即b[ i ]- i <= b[i+1] -(i+1), 那么一开始就把a[ i ] 减去i就可以解决递增的了,然后把数组倒过来求一次就能解决递减的了。


  吐槽, 我写的程序又变得慢=。=!如同lzn说的,左偏树也可以写丑?!......, 从头到尾,貌似没几个算法写得比hyc快些=。=!, 好傻X,好桑心>.<

你可能感兴趣的:(【左偏树】 poj3016)