【左偏树】应用

最近见到有人再度提起“左偏树”这个词

我就重新写了一遍,不过是用与以前不同的动态结构写的,同时也决定以后写这个用动态结构!

而且也学习了内存池免去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;
}



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值