Live2D

Solution -「JOISC 2019」「LOJ #3036」指定城市

Description

  Link.

  给定一棵含 n 个结点的树,双向边权不相同。q 次询问,每次询问在树上标记 e 个点,标记的价值为所有趋向于某个标记点的有向边权值之和,求价值的最大值。

  qn2×105

Solution

  e=1 or 2 的时候可以直接换根求解。需要强调的是,当确定一个被标记的根时,其余标记点的贡献为根到这个标记点的有向路径长度(取并)。接下来引入一些结论。首先有:

  对于一棵根被钦定标记的树(e=1),当 e=k<n 时,一定能通过标记深度最深的结点使得其成为 e=k 时的最优解。

  不难意会。(

  由此可以推出一个关键的结论:

  对于 k>2e=k 时的最优解必然通过在某个 e=k1 时的最优解的基础上新标记一个点得到。

  记 e=k某个最优解标记点集合为 Se=k+1任一最优解的标记点集合为 T,考虑反证,若不存在 ST

  • ST,取一个 rST,标记并作为树根。由上一个结论,矛盾。
  • ST=,考虑把 T 中的一个结点丢到 S 中,此时 S 不会比 T 劣。

  所以,以 e=2 时的一个标记点为根,令每片叶子的权值为其保持作为子树最深点,向上能爬的距离。排序取前 k 大之和加上一些常数就是 e=k 的答案。复杂度 O(nlogn)

Code

/* Clearink */

#include <cstdio>
#include <algorithm>

#define rep( i, l, r ) for ( int i = l, repEnd##i = r; i <= repEnd##i; ++i )
#define per( i, r, l ) for ( int i = r, repEnd##i = l; i >= repEnd##i; --i )

typedef long long LL;
typedef std::pair<int, LL> PIL;

inline int rint() {
	int x = 0, s = getchar();
	for ( ; s < '0' || '9' < s; s = getchar() );
	for ( ; '0' <= s && s <= '9'; s = getchar() ) x = x * 10 + ( s ^ '0' );
	return x;
}

template<typename Tp>
inline void wint( Tp x ) {
	if ( x < 0 ) putchar( '-' ), x = -x;
	if ( 9 < x ) wint( x / 10 );
	putchar( x % 10 ^ '0' );
}

inline LL lmax( const LL a, const LL b ) { return a < b ? b : a; }
inline void chkmax( LL& a, const LL b ) { a < b && ( a = b, 0 ); }

const int MAXN = 2e5;
const LL LINF = 1ll << 60;
int n, ecnt = 1, head[MAXN + 5], rt;
LL all, upsum, wgt[MAXN + 5];
struct Edge { int to, val, nxt; } graph[MAXN * 2 + 5];

inline void link( const int s, const int t, const int w ) {
	graph[++ecnt] = { t, w, head[s] };
	head[s] = ecnt;
}

namespace Subtask23 {

int siz[MAXN + 5], root;
LL sum[MAXN + 5], mx[MAXN + 5], sm[MAXN + 5], ans[2];

inline void init( const int u, const int fa ) {
	siz[u] = 1, mx[u] = 0, sm[u] = -LINF;
	for ( int i = head[u], v; i; i = graph[i].nxt ) {
		if ( ( v = graph[i].to ) ^ fa ) {
			init( v, u );
			siz[u] += siz[v], sum[u] += sum[v] + graph[i ^ 1].val;
			LL d = mx[v] + graph[i].val;
			if ( mx[u] < d ) sm[u] = mx[u], mx[u] = d;
			else if ( sm[u] < d ) sm[u] = d;
		}
	}
}

inline void solve( const int u, const int fa, const LL ups, const LL upx ) {
	chkmax( ans[0], sum[u] + ups );
	LL tmp = sum[u] + ups + lmax( upx, mx[u] );
	if ( ans[1] < tmp ) root = u, ans[1] = tmp;
	for ( int i = head[u], v; i; i = graph[i].nxt ) {
		if ( ( v = graph[i].to ) ^ fa ) {
			int w1 = graph[i].val, w2 = graph[i ^ 1].val;
			LL ns = ups + sum[u] - sum[v] - w2 + w1;
			LL nx = lmax( upx, mx[v] + w1 < mx[u] ? mx[u] : sm[u] ) + w2;
			solve( v, u, ns, nx );
		}
	}
}

inline void main() {
	init( 1, 0 );
	solve( 1, 0, 0, 0 );
}

} // namespace Subtask23.

inline PIL init( const int u, const int fa ) {
	PIL ret( u, 0 );
	for ( int i = head[u], v; i; i = graph[i].nxt ) {
		if ( ( v = graph[i].to ) ^ fa ) {
			PIL tmp( init( v, u ) );
			upsum += graph[i ^ 1].val;
			wgt[tmp.first] += graph[i].val;
			if ( tmp.second + graph[i].val > ret.second ) {
				ret = { tmp.first, tmp.second + graph[i].val };
			}
		}
	}
	return ret;
}

int main() {
	freopen( "city.in", "r", stdin );
	freopen( "city.out", "w", stdout );
	n = rint();
	rep ( i, 2, n ) {
		int u = rint(), v = rint(), a = rint(), b = rint();
		all += a + b;
		link( u, v, a ), link( v, u, b );
	}
	Subtask23::main();
	rt = Subtask23::root;
	init( rt, 0 );
	// printf( "root is %d: ", rt );
	// rep( i, 1, n ) printf( "%lld ", wgt[i] );
	// puts( "" );
	std::sort( wgt + 1, wgt + n + 1, []( const LL a, const LL b ) {
		return a > b;
	} );
	rep( i, 1, n ) wgt[i] += wgt[i - 1];
	for ( int q = rint(), e; q--; ) {
		e = rint();
		wint( all - ( e < 2 ? Subtask23::ans[0] :
			upsum + wgt[e - 1] ) ), putchar( '\n' );
	}
	return 0;
}

posted @   Rainybunny  阅读(148)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示