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,好桑心>.<