bzoj2870. 最长道路tree【点分治】【边分治】

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

传送门
H H H 城很大,有 N N N个路口(从 1 1 1 N N N编号),路口之间有 N − 1 N-1 N1边,使得任意两个路口都能互相到达,这些道路的长度我们视作一样。每个路口都有很多车辆来往,所以每个路口i都有一个拥挤程度 v [ i ] v[i] v[i],我们认为从路口 s s s走到路口 t t t的痛苦程度为 s s s t t t的路径上拥挤程度的最小值,乘上这条路径上的路口个数所得的积。现在请你求出痛苦程度最大的一条路径,你只需输出这个痛苦程度。

简化版描述:
给定一棵 N N N个点的树,求树上一条链使得链的长度乘链上所有点中的最小权值所得的积最大
其中链长度定义为链上点的个数。

分析

对于这种,一个点到其它所有点路径的题(不是子树问题,子树考虑dsu,或者树链剖分),通常考虑点分治,边分治这类算法进行求解
这里介绍两种方法进行求解
另外这是一道经典边分治的题目

最最重要的一点是,无论是点分治还是边分治,都是由(经过和不经过)分类讨论形成的解法,正确性在分类讨论中得到证明,复杂度由重心个数证明。

点分治

点分治过程,我们需要知道两个值,距离和路径最小值
为了避免去重的复杂操作
我们可以先将一棵子树的信息存储起来
当第二棵子树的信息收集好后,再算第一棵与第二棵合并的答案
之后再将第二棵的信息存起来,第三棵到信息获取到的时候,计算第三棵树和前面两个合并的答案,依次递推就能避免去重
如何维护信息呢?
事实上,我们要求 ( a i + a j ) ∗ min ⁡ ( b i , b j ) (a_i+a_j)*\min(b_i,b_j) (ai+aj)min(bi,bj) a a a是长度, b b b为路径上的最小值
b i b_i bi讨论,按照 b i b_i bi进行从小到大排序
要求上面式子的最大值,当我们固定了 b i b_i bi后,只需要找到后面最大的 a j a_j aj即可(排序后,后面的值一定 b j > = b i b_j>=b_i bj>=bi,所以 min ⁡ ( b i , b j ) = b i \min(b_i,b_j)=b_i min(bi,bj)=bi a i a_i ai也确定了,唯一变量就是 a j a_j aj了,所以要找最大值)
如果每个重心只有两个叶子节点(子树)的话,直接排序就行了,但是多个子树的情况 ,需要动态维护这些排序后的值(第一棵+第二棵+第三棵+…)
我们要找的是, b j > = b i b_j>=b_i bj>=bi的最大 a j a_j aj,看到这里可以用权值线段树维护信息,以 b i b_i bi作为下标,维护最大的 a b i a_{b_i} abi
这题边权为 1 1 1,即使成链的话,最大也就是 N = 50000 N=50000 N=50000

这里有个坑点,我们求的是以当前 b i b_i bi为最小值,求前面的最大 a j a_j aj
但是我们少算了,以当前 b i b_i bi为最大值的情况(也就是我当前可能对前面某个 b j b_j bj最小造成贡献)
这个问题又变成了没有排序的那个复杂问题了,但这里我们能够倒着再算一边,达到不漏效果
具体是,用个栈存储前面的子树遍历顺序,后面就倒过来遍历即可

同时我们采用更加优化的方法,对于某个重心来说,其子树节点数能够知道,也就是边权最大不超过这个 s z sz sz,动态开点即可
我们还需要清空线段树,此时采用懒惰标记清空,保证复杂度

边分治

边分治,求的是经过此条边的答案,点分治是,经过这点的答案
类比于点分治找重心,某所有子树,最大的节点个数最小
边分治是,某两端的子树,最大的节点个数最小
可以证明,点分治和边分治复杂度都是 l o g log log级别的
打标记,点分治对点打标记,这里对边打标记,表示这条边已经分治过了,不必分治,所以打标记的这些边和点分治一样,每次都将树进行切分,节点数呈幂次降级

对于边分治来说,因为只有两个子树,所以合并的时候,使用两个 v e c t o r vector vector 来存储两个子树的信息,用其它数据结构也行,两棵子树,不用使用动态的数据结构,相对来说实现复杂度不会很高

对于这题,合并前,同样是对路径按照 b i b_i bi(节点权值最小值)排序,然后,求左子树 b i b_i bi固定为最小的时候,在右子树找 a j a_j aj最大,之后再求右子树 b i b_i bi固定为最小的时候的情况,这样就不漏了,不重也是一定的(只有两棵子树这个性质非常的好),这里使用双指针实现

代码

点分治

//bz2870 
/*
  @Author: YooQ
*/
#include <bits/stdc++.h>
using namespace std;
#define sc scanf
#define pr printf
#define ll long long
#define int long long
#define FILE_OUT freopen("out", "w", stdout);
#define FILE_IN freopen("9.in", "r", stdin);
#define debug(x) cout << #x << ": " << x << "\n";
#define AC 0
#define WA 1
#define INF 0x3f3f3f3f
const ll MAX_N = 2e6+5;
const ll MOD = 1e9+7;
int N, M, K;

int arr[MAX_N];
int uniarr[MAX_N];
int unicnt = 0;

int head[MAX_N];
int tot = 0;
struct Edge{
	int to, nxt;
}edge[MAX_N];

void addEdge(int u, int v) {
	edge[tot].nxt = head[u];
	edge[tot].to = v;
	head[u] = tot++;
	edge[tot].nxt = head[v];
	edge[tot].to = u;
	head[v] = tot++;
}

bool vis[MAX_N];
int sz[MAX_N];
int maxx[MAX_N];

struct Tr {
	int k, lazy;
}tr[MAX_N<<4];

void push_up(int rt) {
	tr[rt].k = max(tr[rt<<1].k, tr[rt<<1|1].k);
}

void push_down(int rt) {
	if (!tr[rt].lazy) return;
	tr[rt<<1].k = 0;
	tr[rt<<1].lazy = 1;
	tr[rt<<1|1].k = 0;
	tr[rt<<1|1].lazy = 1;
	tr[rt].lazy = 0;
}

void clear() {
	tr[1].k = 0;
	tr[1].lazy = 1;
}

void update(int rt, int l, int r, int x, int k) {
	if (l == r) {
		tr[rt].k = max(tr[rt].k, k);
		return;
	}
	push_down(rt);
	int mid = l + ((r-l)>>1);
	if (x <= mid) update(rt<<1, l, mid, x, k);
	if (x  > mid) update(rt<<1|1, mid+1, r, x, k);
	push_up(rt);
}

int query(int rt, int l, int r, int x, int y) {
	if (x <= l && r <= y) {
		return tr[rt].k;
	}
	push_down(rt);
	int mid = l + ((r-l)>>1);
	if (y <= mid) return query(rt<<1, l, mid, x, y);
	if (x  > mid) return query(rt<<1|1, mid+1, r, x, y);
	return max(query(rt<<1, l, mid, x, y), query(rt<<1|1, mid+1, r, x, y));
}

void dfs(int u, int from, int& rt, int sum) {
	sz[u] = 1;
	maxx[u] = 0;
	
	int v;
	for (int i = head[u];~i;i=edge[i].nxt) {
		if (vis[v=edge[i].to] || v == from) continue;
		dfs(v, u, rt, sum);
		sz[u] += sz[v];
		maxx[u] = max(maxx[u], sz[v]);
	}
	
	maxx[u] = max(maxx[u], sum - sz[u]);
	if (maxx[u] < maxx[rt]) {
		rt = u;
	}
}

struct Node {
	int a, b, c;
};

vector<Node>vec;

void get_dis(int u, int from, int minn, int len) {
	vec.push_back({minn, len, u});
	int v;
	for (int i = head[u];~i;i=edge[i].nxt) {
		if (vis[v=edge[i].to] || v == from) continue;
		get_dis(v, u, min(minn, arr[v]), len+1);
	}
}

int ans[MAX_N];
int stk[MAX_N];
int tt = 0;

void work(int u) {
	int v;
	clear();
	tt = 0;
	for (int i = head[u];~i;i=edge[i].nxt) {
		if (vis[v=edge[i].to]) continue;
		stk[++tt] = edge[i].to;
		get_dis(v, u, min(arr[u], arr[v]), 2);
		for (auto x : vec) {
			ans[u] = max(uniarr[x.a] * x.b, ans[u]);
			ans[u] = max(ans[u], uniarr[x.a] * (query(1, 1, unicnt, x.a, unicnt) + x.b - 1));
		}
		for (auto x : vec) {
			update(1, 1, unicnt, x.a, x.b);
		}
		vec.clear();
	}
	
	clear();
	
	while (tt) {
		v = stk[tt--];
		get_dis(v, u, min(arr[u], arr[v]), 2);
		for (auto x : vec) {
			ans[u] = max(uniarr[x.a] * x.b, ans[u]);
			ans[u] = max(ans[u], uniarr[x.a] * (query(1, 1, unicnt, x.a, unicnt) + x.b - 1));
		}
		for (auto x : vec) {
			update(1, 1, unicnt, x.a, x.b);
		}
		vec.clear();
	}
}

void div(int u) {
	vis[u] = 1;
	work(u);
	int v;
	for (int i = head[u];~i;i=edge[i].nxt) {
		if (vis[v=edge[i].to]) continue;
		int rt = 0;
		dfs(v, u, rt, sz[v]);
		div(rt);
	}
}

void init() {
	maxx[0] = 1e9;
	memset(head, -1, sizeof head);
	tot = 0;
}

void solve(){
	init();
	cin >> N;
	
	for (int i = 1; i <= N; ++i) {
		cin >> arr[i];
		uniarr[i] = arr[i];
	}
	
	unicnt = N;
	sort(uniarr+1, uniarr+1+unicnt);
	unicnt = unique(uniarr+1, uniarr+1+unicnt) - uniarr - 1;
	
	for (int i = 1; i <= N; ++i) {
		arr[i] = lower_bound(uniarr+1, uniarr+1+unicnt, arr[i]) - uniarr;
	}
	
	int u, v;
	for (int i = 1; i < N; ++i) {
		cin >> u >> v;
		addEdge(u, v);
	}
	
	int rt = 0;
	maxx[0] = 1e9;
	dfs(1, 0, rt, N);
	div(rt);
	
	int res = 0;
	for (int i = 1; i <= N; ++i) {
		res = max(res, ans[i]);
	}
	cout << res << '\n';
}

signed main()
{
	#ifndef ONLINE_JUDGE
	FILE_IN
	FILE_OUT
	#endif
	int T = 1;//cin >> T;
	while (T--) solve();

	return AC;
}

边分治

//bzoj2870 
/*
  @Author: YooQ
*/
#include <bits/stdc++.h>
using namespace std;
#define sc scanf
#define pr printf
#define ll long long
#define int long long
#define FILE_OUT freopen("out", "w", stdout);
#define FILE_IN freopen("9.in", "r", stdin);
#define debug(x) cout << #x << ": " << x << "\n";
#define AC 0
#define WA 1
#define INF 0x3f3f3f3f
const ll MAX_N = 1e6+5;
const ll MOD = 1e9+7;
int N, M, K;

int arr[MAX_N];
int head[MAX_N];
int tot = 0;
struct Edge{
	int to, nxt;
}edge[MAX_N];

void addEdge(int u, int v) {
	edge[tot].nxt = head[u];
	edge[tot].to = v;
	head[u] = tot++;
	edge[tot].nxt = head[v];
	edge[tot].to = u;
	head[v] = tot++;
}

int sz[MAX_N];
bool vis[MAX_N];
int maxx;

void dfs(int u, int from, int& rt, int sum) {
	sz[u] = 1;
	
	int v;
	for (int i = head[u];~i;i=edge[i].nxt) {
		if ((v=edge[i].to) == from || vis[i]) continue;
		dfs(v, u, rt, sum);
		sz[u] += sz[v];
		
		if (max(sum - sz[v], sz[v]) < maxx) {
			maxx = max(sum - sz[v], sz[v]);
			rt = i;
		}
	}
}

struct Node {
	int a, b;
	bool operator < (const Node& B) const {
		if (a == B.a) return b < B.b;
		return a < B.a;
	}
};

vector<Node>L, R;

void get_dis(vector<Node>& vec, int u, int from, int minn, int len) {
	vec.push_back({minn, len});
	
	int v;
	for (int i = head[u];~i;i=edge[i].nxt) {
		if (vis[i] || (v=edge[i].to) == from) continue;
		get_dis(vec, v, u, min(minn, arr[v]), len+1);
	}
}

int ans = 0;
void divide(int u, int sum) {
	if (sum == 1) return;
	
	int rt = -1;
	maxx = 1e9;
	dfs(u, 0, rt, sum);
	vis[rt] = vis[rt^1] = 1;
	
	int x = edge[rt].to;
	int y = edge[rt^1].to;
	L.clear();R.clear();
	get_dis(L, x, 0, arr[x], 1);
	get_dis(R, y, 0, arr[y], 1);
	
	sort(L.begin(), L.end());
	sort(R.begin(), R.end());
	int tmp = 0;
	int l = L.size() - 1;
	int r = R.size() - 1;
	for (; l+1; --l) {
		while (r+1 && R[r].a >= L[l].a) {
			tmp = max(tmp, R[r--].b);
		}
		ans = max(ans, (tmp + L[l].b) * L[l].a);
	}
	
	tmp = 0;
	l = L.size() - 1;
	r = R.size() - 1;
	for (; r+1; --r) {
		while (l+1 && L[l].a >= R[r].a){
			tmp = max(tmp, L[l--].b);
		}
		ans = max(ans, (tmp + R[r].b) * R[r].a);
	}
	
	if (sz[x] > sz[y]) {
		sz[x] = sum - sz[y];
	} else if (sz[x] < sz[y]) {
		sz[y] = sum - sz[x];
	}
	divide(x, sz[x]);
	divide(y, sz[y]);
}

void init() {
	memset(head, -1, sizeof head);
	tot = 0;
}

void solve(){
	init();
	cin >> N;
	for (int i = 1; i <= N; ++i) {
		cin >> arr[i];
	}
	
	int u, v;
	for (int i = 1; i < N; ++i) {
		cin >> u >> v;
		addEdge(u, v);
	}
	
	divide(1, N);
	
	cout << ans;
}

signed main()
{
	#ifndef ONLINE_JUDGE
	FILE_IN
	FILE_OUT
	#endif
	int T = 1;//cin >> T;
	while (T--) solve();

	return AC;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hexrt

客官,请不要给我小费!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值