[Apio2012][Treap]派遣

1 篇文章 0 订阅
1 篇文章 0 订阅

原题地址


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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值