最近见到有人再度提起“左偏树”这个词
我就重新写了一遍,不过是用与以前不同的动态结构写的,同时也决定以后写这个用动态结构!
而且也学习了内存池免去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;
}