【左偏樹】應用

最近見到有人再度提起“左偏樹”這個詞

我就重新寫了一遍,不過是用與以前不同的動態結構寫的,同時也決定以後寫這個用動態結構!

而且也學習了內存池免去new節點這個很酷的方法!

//插一下,講下內存池

node re[maxn], *tot = re;

利用一個已經分配了地址的re數組節省new的時間

之後要新建節點則

*(++tot) = (node) {中間填初始值};

//

不瞭解左偏樹的可以去看看黃源河的論文

先談談我對“左偏樹”的印象吧

1.合併的複雜度O(logn)完虐二叉堆O(n)

2.編程複雜度與空間消耗完虐Fibonacci堆

3.優美,因爲結合了堆性質與左偏性質

其實因爲左偏樹真心代碼特短,讓我有種想放棄普通堆的衝動,不過常數是要大一些


1. APIO2012 派遣 dispatching

派遣 
【問題描述】 
在一個忍者的幫派裏,一些忍者們被選中派遣給顧客,然後依據自己的工作
獲取報償。 
在這個幫派裏,有一名忍者被稱之爲 Master。除了 Master 以外,每名忍者
都有且僅有一個上級。爲保密,同時增強忍者們的領導力,所有與他們工作相關
的指令總是由上級發送給他的直接下屬,而不允許通過其他的方式發送。 
現在你要招募一批忍者,並把它們派遣給顧客。你需要爲每個被派遣的忍者
支付一定的薪水,同時使得支付的薪水總額不超過你的預算。另外,爲了發送指
令,你需要選擇一名忍者作爲管理者,要求這個管理者可以向所有被派遣的忍者
發送指令,在發送指令時,任何忍者(不管是否被派遣)都可以作爲消息的傳遞
人。管理者自己可以被派遣,也可以不被派遣。當然,如果管理者沒有被派遣,
你就不需要支付管理者的薪水。 
你的目標是在預算內使顧客的滿意度最大。這裏定義顧客的滿意度爲派遣的
忍者總數乘以管理者的領導力,其中每個忍者的領導力也是一定的。 
寫一個程序,給定每一個忍者 i 的上級 Bi,薪水 Ci,領導力 Li,以及支付給
忍者們的薪水總預算 M,輸出在預算內滿足上述要求時顧客滿意度的最大值。 
【數據範圍】 
1 ≤ N ≤ 100,000 
忍者的個數; 
1 ≤ M ≤ 1,000,000,000 
薪水總預算; 
0 ≤ Bi < i 
忍者的上級的編號; 
1 ≤ Ci ≤ M 
忍者的薪水; 
1 ≤ Li ≤ 1,000,000,000 
忍者的領導力。 
 
對於 30%的數據,N ≤ 3000。 
【輸入格式】 
從標準輸入讀入數據。 
第一行包含兩個整數 N 和 M,其中 N 表示忍者的個數,M 表示薪水的總預
算。 
接下來 N 行描述忍者們的上級、薪水以及領導力。其中的第 i 行包含三個整
數 Bi , Ci , Li分別表示第 i 個忍者的上級,薪水以及領導力。Master 滿足 Bi = 0,
並且每一個忍者的上級的編號一定小於自己的編號 Bi < i。 
【輸出格式】 
輸出到標準輸出。 
輸出一個數,表示在預算內顧客的滿意度的最大值。 
【樣例輸入】 
5 4 
0 3 3 
1 3 5 
2 2 2 
1 2 4 
2 3 1 
【樣例輸出】 
6 
【樣例說明】 
如果我們選擇編號爲1的忍者作爲管理者並且派遣編號爲3和編號爲4的忍
者,薪水總和爲 4,沒有超過總預算 4。因爲派遣了 2 個忍者並且管理者的領導
力爲 3,用戶的滿意度爲 2 × 3 = 6,是可以得到的用戶滿意度的最大值。 

題意爲要求找出一個管理者,然後選出其麾下儘可能多的忍者,然後用人數乘以管理者領導力更新最優值

各種暴力算法在當時數據水+清華評測機好的情況下有拿80和100的

其實暴力算法就是利用每次的排序枚舉後代

但我們應該要意識到

如果在i點爲管理者時,其某些後輩節點無法選中,那麼在i點的祖先節點爲管理者時也無法選中!

那麼意味着我們可以維護某點的決策集合

則什麼各種合併數據結構或算法就出現了(如二叉堆,合併時可用大堆合併小堆優化常數,即將小堆中的點一個一個插入大堆)

這也意味着左偏樹能以高效率O(nlogn)解此題

寫代碼時有個小地方讓我調了很久

就是每個堆的大小沒有往上更新

code

#include <algorithm>
#include <ctime>
#include <cmath>
#include <string>
#include <memory>
#include <cstdio>
#include <climits>
#include <cstring>
#include <cstdlib>
#include <iostream>

using namespace std;

const int maxn = 100000 + 5;
const int maxm = 100000 + 5;
const int maxh = 200000 + 5;

typedef int pint[maxn];
typedef int eint[maxm];
typedef unsigned int uint;
typedef long long int64;
typedef unsigned long long uint64;

template <class T> inline T Sqr(T x) { return x * x; }
template <class T> inline T Abs(T x) { return x > 0 ? x : -x; }
template <class T> inline T Min(T a, T b) { return a < b ? a : b; }
template <class T> inline T Max(T a, T b) { return a > b ? a : b; }
template <class T> inline void Swap(T & a, T & b) { T _; _ = a; a = b; b = _; }
template <class T> inline T Ksm(T a, T b, T m) { T _ = 1; for (; b; b >>= 1, a = a * a % m) (b & 1) ? _ = _ * a % m : 0; return _ % m; }

struct node
{
   int c, ld, dist, size;//薪水,領導力
   node *l, *r;
} *tree[maxn], ve[maxh], *tot = ve;

int e, n, m, rt;
int64 ans;
pint edge, c, l, limt; eint next, point;

void link(int u, int v)
{
   point[++e] = v; next[e] = edge[u]; edge[u] = e;
}

node * merge(node *a, node *b)
{
   if (!a) return b; if (!b) return a;
   if (a -> c < b -> c) Swap(a, b);
   a -> r = merge(a -> r, b);
   if (!a -> l || (a -> r && a -> r -> dist > a -> l -> dist)) Swap(a -> l, a -> r);
   if (a -> r) a -> dist = a -> r -> dist + 1; else a -> dist = 0;
   return a;
}

void dfs(int u)
{
   *(++tot) = (node) { c[u], l[u], 0, 1 };
   tree[u] = merge(tree[u], tot);
   int t = tree[u] -> size;
   for (int i = edge[u]; i; i = next[i])
   {
      dfs(point[i]); t += tree[point[i]] -> size;
      tree[u] = merge(tree[u], tree[point[i]]);
      limt[u] += limt[point[i]];
      while (limt[u] > m)  limt[u] -= tree[u] -> c, tree[u] = merge(tree[u] -> l, tree[u] -> r), --t;
      tree[u] -> size = t;//忘記寫的地方
   }
   ans = Max((int64)t * l[u], ans);
}

int main()
{
   freopen("dispatching.in", "r", stdin);
   freopen("dispatching.out", "w", stdout);

   scanf("%d%d", &n, &m);
   for (int i = 1, u; i <= n; ++i)
      scanf("%d%d%d", &u, &c[i], &l[i]), link(u, i), limt[i] = c[i];

   dfs(0), printf("%I64d\n", ans);

   return 0;
}


2.大炮(長郡內部NOIP模擬題)


貪心題,以下是題解,但沒用左偏樹優化

構造思路:首先,需知道有沒有解:

爲了方便描述,記Num[I](I>=0)爲尺寸爲I的盒子個數;T[I](I>=0)爲尺寸爲I的集裝箱個數;

①由於尺寸爲0(大小20)的集裝箱必須由尺寸爲0(大小20)的盒子來填,所以,如果Num[0]比T[0]小則必定無解,否則能把尺寸爲0的集裝箱的填滿;

②由於尺寸爲1(大小21)的集裝箱必須由尺寸爲0或1的盒子來填,並且只能包含偶數個尺寸爲0的組成,而尺寸爲0的盒子還剩下Num[0]-T[0]個,這些盒子顯然可以兩兩組合成(Num[0]-T[0])DIV 2個尺寸爲1的盒子,故可看作總共有(Num[1]+(Num[0]-T[0])DIV 2)個尺寸爲1的盒子,所以Num[1]ß Num[1]+(Num[0]-T[0])DIV 2,Num[0]ß0,在按與第一步類似的方法處理即可;

③以此類推;

④若尺寸爲0~1000的集裝箱都可填滿,則必定有解。

然而,增麼得到最優解呢?

實際上,只需在求可行解時多加一些運算便可以保證可行解爲最優了。

即:1、第k步時,若非無解情況,則選前T[k-1]個價值最小的盒子;

       2、第k步時,剩下Num[k-1]-T[k-1]個盒子,則把它從小到大排序後,讓第2*I-1與2*I(I>=1)個盒子結合成一個尺寸爲k的大盒子。

這樣是可以得到最優值。

證明:若存在可行解,而在某一步k中,選的不是前T[k-1]個價值最小的盒子;我們不妨設取了一個價值爲b的尺寸爲k-1的盒子,且b不是前T[k-1]小的,則必定有一個(或更多)前T[k-1]小的沒取,設它的價值爲a,顯然有b>=a,那我們將原方案中這兩個盒子的位子交換一下(若交換前價值爲a的這個沒有被選,則交換後不選價值爲b的這個盒子),就可能得到更優的解(至少不會差),所以,每次都要選價值最小的前幾個,而第2步也不難明白:這樣組合能使得到的Num[k-1]-T[k-1]DIV 2個盒子的前j(1<=j<=Num[k-1]-T[k-1]DIV 2)小的價值和達到最小,從而使後面的選擇也正確。

 

    執行步驟:

1. 讀入數據;

2. 分別對不同尺寸的盒子按升序排序,存在value[I]數組中(value[I]中按升序保存所有尺寸爲I的盒子的價值);

3. Kß0,Minß0;

4. 若Num[k]<T[k]則轉9;

5. 選出value[k]中1到T[k]個盒子,MinßMin+value[k,1]+value[k,2] +…value[k,T[k]];

6. 若k<1000,則將剩下的T[k]-Num[k]個盒子,兩兩組合成(T[k]-Num[k])DIV 2個尺寸爲k+1的{第T[k]+1與T[k]+2,T[k]+3與T[k]+4…,組合},在此過程中,把新生成的(T[k]-Num[k])DIV 2歸併入value[k+1]中,且Num[k+1]ßNum[k+1]+ (T[k]-Num[k])DIV 2

7. K:=K+1,若k<=1000轉4否則輸出Min;

8. 結束

9. 輸出無解,轉8


code(用左偏樹優化的)

#include <algorithm>
#include <ctime>
#include <cmath>
#include <string>
#include <memory>
#include <cstdio>
#include <climits>
#include <cstring>
#include <cstdlib>
#include <iostream>

using namespace std;

const int maxn = 20000 + 5;

typedef unsigned int uint;
typedef long long int64;
typedef unsigned long long uint64;

template <class T> inline T Sqr(T x) { return x * x; }
template <class T> inline T Abs(T x) { return x > 0 ? x : -x; }
template <class T> inline T Min(T a, T b) { return a < b ? a : b; }
template <class T> inline T Max(T a, T b) { return a > b ? a : b; }
template <class T> inline void Swap(T & a, T & b) { T  _ = a; a = b; b = _; }
template <class T> inline T Ksm(T a, T b, T m) { T _ = 1; for (; b; b >>= 1, a = a * a % m) (b & 1) ? _ = _ * a % m : 0; return _ % m; }

struct node
{
   int w, dist;
   node *l, *r;
} *tree[maxn], ve[maxn], * tot = ve;

int n, m, t[maxn], limt, total;

node * merge(node *a, node *b)
{
   if (!a) return b; 
   if (!b) return a;
   if (a -> w > b -> w) Swap(a, b);
   a -> r = merge(a -> r, b);
   if (!a -> l || (a -> r && (a -> l -> dist) < (a -> r -> dist))) Swap(a -> l, a -> r);
   if (a -> r) a -> dist = a -> r -> dist + 1; else a -> dist = 0;
   return a;
}

int main()
{
   freopen("noligon.in", "r", stdin);
   freopen("noligon.out", "w", stdout);

   scanf("%d", &n);
   for (int i = 1, k, w; i <= n && scanf("%d%d", &k, &w); ++i)
       *(++tot) = (node) { w, 0, 0, 0 }, tree[k] = merge(tree[k], tot);

   scanf("%d", &m);
   for (int i = 1, g, h; i <= m; ++i)
   {
      scanf("%d%d", &g, &h); t[g] += h;
      limt = Max(g, limt);
   }

   for (int i = 0; i <= limt; ++i)
   {
      for (int j = 1; j <= t[i]; ++j)
      {
         if (!tree[i]) puts("-1"), exit(0);
         total += tree[i] -> w;
         tree[i] = merge(tree[i] -> l, tree[i] -> r);
      }
      t[i] = 0;
      for (int t = 0; tree[i]; t = 0)
      {
         t += tree[i] -> w;
         tree[i] = merge(tree[i] -> l, tree[i] -> r);
         if (!tree[i]) break;
         t += tree[i] -> w;
         tree[i] = merge(tree[i] -> l, tree[i] -> r);
         *(++tot) = (node) { t, 0, 0, 0 };
         tree[i + 1] = merge(tree[i + 1], tot);
      }
   }

   printf("%d\n", total);

   return 0;
}



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