2020牛客暑期多校训练营(第四场) A:Ancient Distance(贪心 + 线段树 + 思维)

2020牛客暑期多校训练营(第四场) A:Ancient Distance(贪心 + 线段树 + 思维)_第1张图片


题目大意:在有根树上选择 k 个关键点,定义 f(x) 表示 x 到 root 的路径上,最近关键点的距离(若 x 是关键点,则距离为0)。对于 k ∈ [ 1 , n ] k \in[1,n] k[1,n],选择 k 个点时, f ( x ) f(x) f(x) 的最大值要尽可能小。输出对于所有的 k , f ( x ) f(x) f(x) 最大值尽可能小的和。


当 k 取某一个值时,有一个的二分 + 贪心做法:二分答案 x,每次从树上选择一个深度最深的叶子节点,将距离其 x 的祖先标记为关键点,并删除掉这个关键点的子树,通过这样贪心可以得到答案为 x 时,至少需要几个关键点。

注意到这个贪心的过程,当答案为 x 时,每选一个关键点,至少会删除 x + 1 x + 1 x+1 个节点,那么关键点的数量最多为 n x + 1 + 1 \displaystyle\frac{n}{x + 1} + 1 x+1n+1,因为根节点一定要选。

如果用线段树维护 区间 dfs 序的深度最大的节点,模拟这个贪心过程的复杂度为 O ( ( n x + 1 + 1 ) log ⁡ n ) O((\displaystyle\frac{n}{x + 1} + 1)\log n) O((x+1n+1)logn)

注意到如果枚举答案,对于答案 i i i 至少需要的关键点的数量是一个调和级数,而答案显然在 [ 0 , n ] [0,n] [0,n] 内,由于 0 不产生贡献,从 1 开始从小到大枚举答案 i i i,当前这个答案至少需要 c u r cur cur 个关键点,而答案 i − 1 i - 1 i1 至少需要 l a s t c u r lastcur lastcur 个节点,那么可以计算答案 i i i 的贡献: ( l a s t c u r − c u r ) ∗ i (lastcur - cur) * i (lastcurcur)i

不难发现这样做的复杂度为 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n),一个 l o g log log 来自线段树,一个 l o g log log 来自调和级数。

对于每个答案复原线段树,用栈记录每次修改操作带来的节点权值变化,逆序将节点权值还原即可。


代码:

#include
using namespace std;
const int maxn = 2e5 + 10;
#define pii pair
#define fir first
#define sec second
typedef long long ll;
vector<int> g[maxn];
vector<pair<int,pii> > op[maxn];
int n, deep[maxn], st[maxn], id[maxn], ed[maxn], cnt, tot;
int p[maxn][20];
struct seg_tree {
	#define lson rt << 1, l, mid
	#define rson rt << 1 | 1, mid + 1, r
	int tag[maxn << 2], pos[maxn << 2];
	void build(int rt,int l,int r) {
		tag[rt] = 0, pos[rt] = 0;
		if (l == r) {
			pos[rt] = id[l];
			return ;
		}
		int mid = l + r >> 1;
		build(lson); build(rson);
		if (deep[pos[rt << 1 | 1]] > deep[pos[rt << 1]])
			pos[rt] = pos[rt << 1 | 1];
		else
			pos[rt] = pos[rt << 1];
	}
	void pushdown(int rt) {
		if (!tag[rt]) return ;
		tag[rt << 1] = tag[rt << 1 | 1] = tag[rt];
		pos[rt << 1] = pos[rt << 1 | 1] = 0;
		tag[rt] = 0;
	}
	void del(int L,int R,int rt,int l,int r) {
		op[tot].push_back(make_pair(rt,pii(pos[rt],tag[rt])));
		if (L > R || L == 0 || R == 0 || L > n || R > n) return ;
		if (L <= l && r <= R) {
			tag[rt] = 1;
			pos[rt] = 0;
			return ;
		}
		pushdown(rt);
		int mid = l + r >> 1;
		if (L <= mid) del(L,R,lson);
		if (mid + 1 <= R) del(L,R,rson);
		if (deep[pos[rt << 1 | 1]] > deep[pos[rt << 1]])
			pos[rt] = pos[rt << 1 | 1];
		else
			pos[rt] = pos[rt << 1];
	}
}tree;
void dfs1(int u) {
	st[u] = ++cnt; id[cnt] = u;
	for (auto it : g[u]) {
		p[it][0] = u;
		deep[it] = deep[u] + 1;
		dfs1(it);
	}
	ed[u] = cnt;
}
void dfs2(int u) {
	for (int i = 1; i <= 17; i++)
		p[u][i] = p[p[u][i - 1]][i - 1];
	for (auto it : g[u])
		dfs2(it);
}
int getdis(int x,int y) {
	int ans = x, r = 0;
	while (y) {
		if (y & 1) ans = p[ans][r];
		r++;
		y >>= 1;
	}
	return ans;
}
int get_ans(int x) {			//答案为 x 时需要的关键点的个数
	tot = 0;
	while (tree.pos[1]) {
		tot++;
		int v = getdis(tree.pos[1],x);
		if (v == 0) v = 1;
		tree.del(st[v],ed[v],1,1,n);
	}
	for (int i = tot; i >= 1; i--) {
		for (auto it : op[i]) {
			tree.pos[it.first] = it.second.first;
			tree.tag[it.first] = it.second.second;
		}
		op[i].clear();
	}
	return tot;
}
int main() {
	deep[1] = 1; p[1][0] = 0;
	while (~scanf("%d",&n)) {
		cnt = 0; deep[1] = 1;
		for (int i = 1; i <= n; i++)
			g[i].clear();
		for (int i = 2, x; i <= n; i++) {
			scanf("%d",&x);
			g[x].push_back(i);
		}
		dfs1(1); dfs2(1);
		tree.build(1,1,n);
		ll ans = 0;
		int lst = n;
		for (int i = 1; i <= n; i++) {
			int cur = get_ans(i);	//答案为 i至少要几个关键点 
			ans += 1ll * (lst - cur)  * i;
			lst = cur;
		}
		printf("%lld\n",ans);
	}
	return 0;
}

你可能感兴趣的:(2020牛客暑期多校训练营(第四场) A:Ancient Distance(贪心 + 线段树 + 思维))