您的位置:首页 > 其它

[Apio2012][Treap]派遣

2016-03-31 22:01 225 查看
原题地址

题意:在树中找到一个点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
,l
;

struct Edge {
int to;
Edge *next;
Edge () {}
Edge (int to,Edge *next) : to(to),next(next) {}
}ME
,*Po = ME,*head
;

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
,meme
,*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
, l
;
long long ans;

struct Edge {
int to;
Edge *next;
Edge () {}
Edge (int to, Edge *next) : to(to),next(next) {}
}*head
,Me
,*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
, meme
, *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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: