CF526G Spiders Evil Plan

Address

CF526G

Solution

因为边权都是正的,路径一定都会延伸到叶子(无根树中指度数为 1 1 1 的点)。

容易证明,对于任意的 2 y 2y 2y 个叶子,一定存在一种方案使得 y y y 条路径能够覆盖这 2 y 2y 2y 个叶子且路径的并是一个连通块, y y y 条路径能够覆盖的叶子数量的上限也是 2 y 2y 2y

于是问题变为选出 2 y 2y 2y 个叶子,使得叶子两两之间的路径的并包含的边权和最大。

首先如果树中叶子数量大于等于 2 y 2y 2y,可以直接输出所有边的边权和。

否则因为题目要求包含点 x x x,一个暴力的做法就是以 x x x 为根,贪心选 2 y − 1 2y - 1 2y1 个叶子。

对此有一个经典的长链剖分的做法, 令一条长链的权值为长链中所有结点连向其父结点的边的边权和,注意到一个结点的子树中如果有叶子被选,那么该结点所在的长链一定被选。

于是我们只要将长链按权值从大到小排序,取前 2 y − 1 2y - 1 2y1 个即可。

显然数据范围不允许我们枚举根,但可以由调整法证明,包含点 x x x 的最优方案一定至少包含直径的一个端点。

现在只要处理直径的两个端点为根的情况,但此时就不一定能保证包含点 x x x

若前 2 y − 1 2y - 1 2y1 个长链不包含点 x x x,我们就需要去掉尽量少并且权值和尽量小的长链来保证包含 x x x

可以发现,只有以下两种情况:

  1. 删去第 2 y − 1 2y - 1 2y1 个长链,并取出 x x x 所在长链,不断向上延伸直到触碰到前 2 y − 2 2y - 2 2y2 个长链;
  2. 取出 x x x 所在长链,不断向上延伸直到触碰到前 2 y − 1 2y - 1 2y1 个长链,把触碰到的长链的下半段改为连向 x x x 的方向。

记录每个长链的排名,倍增计算即可。

时间复杂度 O ( n log ⁡ n + q log ⁡ n ) \mathcal O(n \log n + q \log n) O(nlogn+qlogn)

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	char ch;
	while (ch = getchar(), !isdigit(ch));
	res = ch ^ 48;
	while (ch = getchar(), isdigit(ch))
		res = res * 10 + ch - 48;
}

template <class T>
inline void put(T x)
{
	if (x > 9)
		put(x / 10);
	putchar(x % 10 + 48);
}

using std::pair;
using std::vector;
const int N = 1e5 + 5;
const int M = 2e5 + 5;
int que[N]; 
bool vis[N];
int n, q, z, tot, qr, opt;

struct Edge
{
	int to, cst; Edge *nxt;
}p[M], *lst[N], *P = p;

inline void Link(int x, int y, int z)
{
	(++P)->nxt = lst[x]; lst[x] = P; P->to = y; P->cst = z;
	(++P)->nxt = lst[y]; lst[y] = P; P->to = x; P->cst = z;
}

template <class T>
inline T Max(T x, T y) {return x > y ? x : y;}

struct tree
{
	pair<int, int> pos[N];
	int anc[N][20], dis[N];
	int cst[N], pre[N], mx[N], son[N];
	int ed[N], _rank[N], id[N], sum[N];
	int rt, T;

	inline void findRoot(int src)
	{
		rt = src;
		for (int i = 1; i <= n; ++i)
			vis[i] = false;
		que[qr = 1] = src;
		vis[src] = true;
		dis[src] = 0;
		for (int i = 1, x, y; i <= qr; ++i)
		{
			x = que[i];
			for (Edge *e = lst[x]; e; e = e->nxt)
				if (y = e->to, !vis[y])
				{
					que[++qr] = y;
					vis[y] = true;
					dis[y] = dis[x] + e->cst;
					if (dis[y] > dis[rt])
						rt = y;
				}
		}
	}

	inline void dfs1(int x)
	{
		for (int i = 0; anc[x][i]; ++i)
			anc[x][i + 1] = anc[anc[x][i]][i];
		for (Edge *e = lst[x]; e; e = e->nxt)
		{
			int y = e->to;
			if (y == anc[x][0])
				continue ;
			anc[y][0] = x;
			cst[y] = e->cst;
			dis[y] = dis[x] + cst[y];
			dfs1(y);

			int tmp = mx[y] + cst[y];
			if (tmp > mx[x])
				mx[x] = tmp, son[x] = y;
		}
	} 

	inline void dfs2(int x)
	{
		if (son[x])
		{
			id[son[x]] = id[x];
			sum[id[x]] += cst[son[x]];
			dfs2(son[x]);
		}
		else
			ed[id[x]] = x;
		for (Edge *e = lst[x]; e; e = e->nxt)
		{
			int y = e->to;
			if (y == anc[x][0] || y == son[x])
				continue ;
			id[y] = ++T;
			sum[T] = cst[y];
			dfs2(y);
		}
	}

	inline void init()
	{
		dis[rt] = 0;
		dfs1(rt);
		id[rt] = T = 1;
		dfs2(rt);

		for (int i = 1; i <= T; ++i)
			pos[i] = std::make_pair(sum[i], i);
		std::sort(pos + 1, pos + T + 1);
		std::reverse(pos + 1, pos + T + 1);
		for (int i = 1; i <= T; ++i)
			pre[i] = pre[i - 1] + pos[i].first;
		for (int i = 1; i <= T; ++i)
			_rank[pos[i].second] = i;
	}

	inline int query(int x, int y)
	{
		int u = x;
		for (int i = 16; i >= 0; --i)
			if (anc[u][i] && _rank[id[anc[u][i]]] > y)
				u = anc[u][i]; 
		u = anc[u][0];
		return Max(pre[y - 1], pre[y] - mx[u]) + dis[ed[id[x]]] - dis[u];
	}

	inline int calc(int x, int y)
	{
		return _rank[id[x]] <= y ? pre[y] : query(x, y);
	}
}t1, t2;

int main()
{
	read(n); read(q); opt = 1;
	for (int i = 1, x, y, z; i < n; ++i)
	{
		read(x); read(y); read(z);
		Link(x, y, z);
		tot += z;
	}
	t1.findRoot(1);
	t2.findRoot(t1.rt);

	t1.init();
	t2.init();
	int last_ans = 0, x, y;
	while (q--)
	{
		read(x); read(y);
		if (opt)	
		{
			x = (x + last_ans - 1) % n + 1;
			y = (y + last_ans - 1) % n + 1;
		}
		y = (y << 1) - 1;
		put(last_ans = y >= t1.T ? tot :
			Max(t1.calc(x, y), t2.calc(x, y))), putchar('\n');
	}
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值