【左偏樹】 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,好桑心>.<

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章