2021牛客多校第一场 C——Cut the Tree

题目大意

给你一棵树,每个点都有一个权值,让你在所有点中选择一个点删除,使得剩下的子树的最长上升子序列长度最小

解题思路

首先如何求树上最长上升子序列

参考博客

如何删点

首先,我们任取一个点 p p p 考虑删除点 p p p 的结果,删除点 p p p 之后,我们只需要考虑 p p p 的子树的最长上升子序列长度,即为答案,同时我们记录一下是哪一棵子树的最长上升子序列最长,我们如果要删其他点的话,那要删去的点一定在这棵子树之中(因为你再删除了这棵子树上的点的其他点是没有意义的,并不会对答案造成贡献)。
然后,如何选取 p p p 显然,我们每次选取要删点的树的重心即可,不然出现一条单链的话复杂度不够。
这样我们的复杂度是 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

#include <bits/stdc++.h>
#define ll long long
#define qc ios::sync_with_stdio(false); cin.tie(0);cout.tie(0)
#define fi first
#define se second
#define PII pair<int, int>
#define PLL pair<ll, ll>
#define pb push_back
using namespace std;
const int MAXN = 1e5 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9 + 7;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
    return x*f;
}
int head[MAXN];
int val[MAXN];
int n;
int ans;
struct edge{
	int next, to, w;
}e[MAXN << 1];
int cnt;
void init(){
	for(int i = 0; i <= n; i++)
		head[i] = -1;
	cnt = 0;
}
void add(int u, int v, int w = 0){
	e[cnt].w = w;
	e[cnt].to = v;
	e[cnt].next = head[u];
	head[u] = cnt++;
};
int tot;
struct Tree{
	int l, r;
	int lis, lds;
}tree[MAXN * 600];
int rt[MAXN];
int sz[MAXN];
int root, maxPart;
int res;
int vis[MAXN];
void getrt(int S, int u, int f){
	sz[u] = 1;
	int maxx = 0;
	for(int i = head[u]; ~i; i = e[i].next){
		int v = e[i].to;
		if(vis[v] || f == v)
			continue;
		getrt(S, v, u);
		sz[u] += sz[v];
		maxx = max(maxx, sz[v]);
	}
	maxx = max(maxx, S - sz[u]);
	if(maxx < maxPart){
		maxPart = maxx;
		root = u;
	}
}
int build(){
	tot++;
	tree[tot].l = tree[tot].r = tree[tot].lis = tree[tot].lds = 0;
	return tot;
}
void update(int &p, int l, int r, int pos, int vlis, int vlds){
	if(p == 0)
		p = build();
	tree[p].lis = max(tree[p].lis, vlis);
	tree[p].lds = max(tree[p].lds, vlds);
	if(l == r)
		return ;
	int m = (l + r) >> 1;
	if(pos <= m)
		update(tree[p].l, l, m, pos, vlis, vlds);
	else
		update(tree[p].r, m+1, r, pos, vlis, vlds);
}
int merge(int p, int q){
	if(!p || !q)
		return p + q;
	tree[p].lis = max(tree[p].lis, tree[q].lis);
	tree[p].lds = max(tree[p].lds, tree[q].lds);
	// 最大上升子序列长度
	res = max(res, max(tree[tree[p].l].lis + tree[tree[q].r].lds, 
		tree[tree[p].r].lds + tree[tree[q].l].lis));
	tree[p].l = merge(tree[p].l, tree[q].l);
	tree[p].r = merge(tree[p].r, tree[q].r);
	return p;
}
PII query(int p, int l, int r, int L, int R){ // L R 询问
	if(!p || l > r)
		return {0, 0};
	if(L <= l && r <= R)
		return {tree[p].lis, tree[p].lds};
	int m = (l + r) >> 1;
	PII ret = {-inf, -inf};
	PII tr, tl;
	if(L <= m)
		tl = query(tree[p].l, l, m, L, R);
	if(m < R)
		tr = query(tree[p].r, m+1, r, L, R);
	ret.fi = max(tl.fi, tr.fi);
	ret.se = max(tl.se, tr.se);
	return ret;
}
void dfs(int u, int f){
	rt[u] = 0;
	for(int i = head[u]; ~i; i = e[i].next){
		int v = e[i].to;
		if(v == f)
			continue;
		dfs(v, u);
	}
	int nlis = 0, nlds = 0;
	for(int i = head[u]; ~i; i = e[i].next){
		int v = e[i].to;
		if(v == f)
			continue;
		int lis = query(rt[v], 1, n, 1, val[u]-1).fi;
		int lds = query(rt[v], 1, n, val[u]+1, n).se;
		rt[u] = merge(rt[u], rt[v]);
		res = max(res, lis + 1 + nlds);
		res = max(res, nlis + 1 + lds);
		nlis = max(nlis, lis);
		nlds = max(nlds, lds);
	}
	update(rt[u], 1, n, val[u], nlis + 1, nlds + 1);
}
void calc(int S, int u){
	maxPart = S;
	getrt(S, u, 0);
	vis[root] = 1;
	int maxx = 0, pos = -1;
	for(int i = head[root]; ~i; i = e[i].next){
		int v = e[i].to;
		res = 0;
		dfs(v, root);
		if(res > maxx){
			maxx = res;
			pos = v;
		}
	}
	ans = min(ans, maxx);
	if(pos == -1 || vis[pos])
		return ;
	calc(sz[pos], pos);
}
void solve(){
	cin >> n;
	init();
	for(int i = 1; i < n; i++){
		int u, v;
		cin >> u >> v;
		add(u, v);
		add(v, u);
	}
	for(int i = 1; i <= n; i++)
		cin >> val[i];
	ans = inf;
	calc(n, 1);
	cout << ans << endl;
}

int main()
{
	#ifdef ONLINE_JUDGE
    #else
       freopen("in.txt", "r", stdin);
       freopen("out.txt", "w", stdout);
    #endif
	qc;
    int T;
    // cin >> T;
    T = 1;
    while(T--){

        solve();
    }
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值