[Apio2012][Treap]派遣

来源:互联网 发布:英雄联盟mac国服下载 编辑:程序博客网 时间:2024/06/10 08:48

原题地址


题意:在树中找到一个点i,并且找到这个点子树中的一些点组成一个集合,使得集合中的所有点的c之和不超过M,且Li*集合中元素个数和最大。


现在有三种做法

很显然的贪心策略:对于每个点,我们把每个以他为代表的子树里的所有点,从小到大排好序。然后一直选小的,直到不满足条件为止。

我们从叶子到根进行合并即可。


法一:

平衡树,对于本蒟蒻来说,当然选择treap了,对于两个将被合并的treap,我们不能保证它们相对有序,因此只能使用启发式合并,即每次合并时把小的treap中的元素一个一个塞到大的里。复杂度 : 由于是把小到塞到大的里,那么那棵小的子树的大小肯定至少变为原来的两倍,因此对于每个元素,至多合并logn次,一共合并nlogn次,每次合并复杂度logn。因此全局合并的复杂度nlog方。我还小小的优化了一下:对于每个即将被合并的节点,由于合并后只会选总和不超过m的那么多个节点,所以当前被合并的节点也只有可能总和不超过m的前面的节点被合并后的节点利用,因此我们还可以在每个节点更新了答案之后,分裂一下,使它里面元素的总和恰好小于等于m。


上代码

#include<cstdio>#include<algorithm>#include<cstring>#include<cstdlib>#include<iostream>#include<string>#include<cmath>#include<cctype>const int N = 1e5 + 9;int n,m,c[N],l[N];struct Edge {int to;Edge *next;Edge () {}Edge (int to,Edge *next) : to(to),next(next) {}}ME[N],*Po = ME,*head[N];void AddEdge (int u,int v) {head [u] = new (Po++) Edge (v, head[u]);}struct Treap {int data, size, hr; long long sum;Treap *l, *r;Treap (int data, Treap *fl) : data(data),size(1),hr(rand()),sum(data),l(fl),r(fl) {}Treap () {}void update() { size = l -> size + r -> size + 1; sum = l -> sum + r -> sum + data; }}*Null,*root[N],meme[N],*pool = meme;Treap *newnode (int xxx) {return new (pool++) Treap (xxx, Null);}Treap *Merge (Treap *A, Treap *B) {if (A == Null) return B;if (B == Null) return A;if (A -> hr > B -> hr) {B -> l = Merge (A, B -> l);B -> update ();return B;} else {A -> r = Merge (A -> r, B);A -> update ();return A;}}using std :: pair;typedef pair <Treap*, Treap*> Droot;Droot Split_Rank (Treap *x, int k) {if (x == Null) return Droot (Null, Null);Droot y;if (x -> l -> size >= k) {y = Split_Rank (x -> l, k);x -> l = y . second;x -> update ();y . second = x;} else {y = Split_Rank (x -> r, k - x -> l -> size - 1);x -> r = y . first;x -> update ();y . first = x;}return y;}Droot Split_Data (Treap *x, int k) {if (x == Null) return Droot (Null, Null);Droot y;if (x -> data >= k) {y = Split_Data (x -> l, k);x -> l = y . second;x -> update ();y . second = x;} else {y = Split_Data (x -> r, k);x -> r = y . first;x -> update ();y . first = x;}return y;}// ÆäʵÄØ£¬ ÎÞÂÛÊǶѻ¹ÊÇƽºâÊ÷£¬Æô·¢Ê½¶¼ÊÇlognµÄ void Insert (Treap *&gen, Treap* xxx) {static Droot clc1;clc1 = Split_Data (gen, xxx -> data);gen = Merge (clc1 . first, Merge (xxx, clc1.second));}int Ran (Treap *x, int& pac) {if (x == Null) return 0; int sum;if (x -> l -> sum <= pac) pac -= x -> l -> sum, sum = x -> l -> size;else sum = Ran (x -> l, pac);if (pac >= x -> data) pac -= x -> data, ++sum;if (pac >= x -> data) sum += Ran (x -> r, pac);return sum;}long long ans;int jl;Treap *u,*v;Droot clc1;void Dfs_Merge (int x) {if(head[x]) {for (Edge *now = head[x]; now; now = now -> next) {Dfs_Merge (now -> to);u = root[x]; v = root[now -> to]; // ¿¼ÂǺϲ¢uºÍv...if (u -> size > v -> size) std :: swap (u, v); // È·±£uС£¬v´ó while (u -> size) {clc1 = Split_Rank (u, 1);u = clc1 . second;Insert (v, clc1.first);}root[x] = v;}Insert (root[x],newnode(c[x]));int M = m;int ran = Ran (root[x], M);root[x] = Split_Rank (root[x],ran) . first;ans = std :: max (ans, 1ll * ran * l[x]);} else {root [x] = newnode (c[x]);ans = std :: max (ans, 1ll * l[x]);}}void Init () {Null = new Treap (); Null -> size = 0;int b;scanf ("%d%d",&n,&m);for (int i=1;i<=n;++i) {scanf ("%d%d%d",&b,c+i,l+i);AddEdge (b,i);root[i] = Null;}}int main () {Init ();Dfs_Merge (1);printf("%lld\n",ans);return 0;}

法二:

很显然的,还可以使用左偏树秒杀,左偏树每次合并不必保证相对有序,所以合并总复杂度nlogn的。。。

但有个问题,如何求左偏树的前k个的大小和呢?

这是个极其蛋疼的问题,所以我们可以转化一下,对于每个左偏树内节点,记下它所在子树的和的大小,把当前的左偏树建成一个小根堆(大的在最上面),不断地弹出,直到元素之和小于等于m。对于每个结点,最多被弹出一次,每次logn 所以弹出的总复杂度是nlogn。。

整个程序的复杂度就是nlogn辣。

上代码

#include<cstdio>#include<cstring>#include<string>#include<cstdlib>#include<iostream>#include<algorithm>#include<cctype>#include<cmath>const int N = 1e5 + 9;int n, m, c[N], l[N];long long ans;struct Edge {int to;Edge *next;Edge () {}Edge (int to, Edge *next) : to(to),next(next) {}}*head[N],Me[N],*Po = Me;void AddEdge (int u, int v) {head[u] = new (Po++) Edge (v, head[u]);}struct LT {int size, dist, data; long long sum;LT *l, *r;LT () {}LT (int data, LT *fl) : size(1),dist(0),data(data),sum(data),l(fl),r(fl) {}void update () { sum = l -> sum + r -> sum + data; dist = r -> dist + 1; size = l -> size + r -> size + 1; }}*Null, *root[N], meme[N], *pool = meme;LT *newnode (int xxx) {return new (pool++) LT (xxx, Null);}LT *Merge (LT *A, LT *B) {if (A == Null) return B;if (B == Null) return A;if (A -> data < B -> data) std :: swap (A, B);A -> r = Merge (A -> r, B);if (A -> r -> dist > A -> l -> dist) std :: swap (A -> l, A -> r);A -> update ();return A;}void Dfs_Merge (int x) {for (Edge *now = head[x]; now; now = now -> next) {Dfs_Merge (now -> to);root[x] = Merge (root[x], root[now -> to]);}root[x] = Merge (root[x], newnode(c[x]));while (root[x] -> sum > m) {root[x] = Merge (root[x] -> l,root[x] -> r);}ans = std :: max (ans , 1ll * root[x] -> size * l[x]);}void Init () {Null = new LT(); Null -> size = Null -> dist = Null -> sum = Null -> data = 0;scanf ("%d%d",&n,&m);int ooo;for (int i = 1; i<= n; ++ i) scanf ("%d%d%d",&ooo,&c[i],&l[i]), AddEdge (ooo, i) , root[i] = Null;}int main () {Init ();Dfs_Merge (1);printf ("%lld\n",ans);return 0;}

法三 莫队加权值分块,根号+根号,暴力一加一

首先我们建出dfs序,这是树形转线性的一种方法。

那么题目就变为查询很多段连续区间,求所有区间中总和加上小于等于m的点的最大个数。 (可能表达地不是很好)

那么就可以用到莫队来处理询问了。

我们发现,最多有1e5个点,因此我们可以离散化,然后权值分块,(这里的权值就是指块状每个节点相当于一个值有多少个)

每次查询根号n,移动总共n根号n,总复杂度 n 根号 n

1 0