【日常训练】配对(贪心)

题目大意:

在这里插入图片描述
在这里插入图片描述
补充说明:这里的不相交指的是每一个特殊点属于且仅属于一个对,不是说在树上的路径不相交。

Solution

  • 一道非常傻的贪心,但是我太弱了没做出来。
  • 其实一句话就能讲完但是我太弱了想多说说。
  • 先说说我的弱鸡思路。
  • 我的思路大概是先确定一个根,记 d u d_u du u u u 到根的距离。
  • 那么所有点对 ( u i , v i ) (u_i,v_i) (ui,vi) 的贡献即为: max ⁡ ∑ d u i + d v i − 2 d l c a ( u i , v i ) \max\sum{d_{u_i}+d_{v_i}-2d_{lca(u_i,v_i)}} maxdui+dvi2dlca(ui,vi)
  • 也就是 ∑ d − 2 min ⁡ ∑ d l c a ( u i , v i ) \sum d-2\min\sum{d_{lca(u_i,v_i)}} d2mindlca(ui,vi)
  • 显然选哪个根是不影响最优性的。
  • 我们要做的是,最小化所有点对 l c a lca lca d d d 的和。我一个开始的想法是从下往上 D P DP DP,但是时间复杂度是 O ( k 3 ) O(k^3) O(k3) 的,并且求方案也很麻烦。
  • 还有一个想法就是从上往下贪心,但是想要保证正确性不好做到。
  • 于是我这题就 G G GG GG 了。拿了高达 10 10 10 分,还有 10 10 10 分应该是我爆搜写丑了,T掉了。

  • 考完发现这是这场的签到题。
  • 我的思路已经很接近了……就是有一个地方脑残了没想清楚。
  • 我们看到 ∑ d − 2 min ⁡ ∑ d l c a ( u i , v i ) \sum d-2\min\sum{d_{lca(u_i,v_i)}} d2mindlca(ui,vi) 这个式子不免想到如果我们能钦点 ∑ d l c a ( u i , v i ) = 0 \sum{d_{lca(u_i,v_i)}}=0 dlca(ui,vi)=0,那么我们就能取到一个肯定是最优的解。
  • 这要求每个点对的两个点都来自根的两棵子树。
  • 满足这一点的充要条件就是根最大的子树的大小不超过 k 2 \frac{k}{2} 2k,这里大小定义为子树包含的特殊点数量。
  • 如果满足这个条件,我们可以每次把两个大小最大的子树拿出一对点配对。这样一定能配完。
  • 然后我们发现如果选 1 1 1 为根是不一定满足这个条件的。
  • 但是这个条件在什么时候能满足呢?
  • 喵的你选重心为根不就好了 Q A Q QAQ QAQ
  • 然后就做完了。
  • 如果你按那个贪心策略写是 O ( n log ⁡ n ) O(n\log n) O(nlogn) 的。不过我们可以线性实现。
  • 具体地,我们先找到重心,然后以重心为根求出 d f s dfs dfs 序。
  • 然后对于 d f s dfs dfs i ≤ k 2 i\le \frac{k}{2} i2k 的,我们只要和 i + k 2 i+\frac{k}{2} i+2k 配对就好。
  • 根据树的重心的性质, i i i i + k 2 i+\frac{k}{2} i+2k 一定来自不同子树。
  • 时间复杂度 O ( n ) O(n) O(n)

  • 该题如果从另一个方向想也是可以的,压根不需要推这种式子。
  • 我们考虑对于一条边,它对答案的贡献次数有个上界 min ⁡ ( x , y ) \min(x,y) min(x,y) x , y x,y x,y 是这条边两侧的特殊点数量。
  • 通过选取重心为根,钦点每对点来自不同子树,就可以取到这个上界。
  • 为啥呢,我们考虑一条边 ( u , f a u ) (u,fa_u) (u,fau),其中以重心为根 f u f_u fu u u u 的深度小。
  • 然后我们根据这个重心的性质,点数较少的一侧必然为 u u u 的子树。又因为 u u u 的子树里面的所有特殊点都连到外面(连到别的子树)了。因此取到了上界。
  • 然后就可以证明这样做是最优的了。
#include <bits/stdc++.h>

template <class T>
inline void read(T &x)
{
	static char ch; 
	while (!isdigit(ch = getchar())); 
	x = ch - '0'; 
	while (isdigit(ch = getchar()))
		x = x * 10 + ch - '0'; 
}

template <class T>
inline bool relax(T &x, const T &y)
{
	return x < y ? x = y, true : false; 
}

template <class T>
inline bool tense(T &x, const T &y)
{
	return x > y ? x = y, true : false; 
}

const int MaxLog = 19; 
const int MaxNV = 1e5 + 5; 
const int INF = 0x3f3f3f3f; 
const int MaxNE = MaxNV << 1; 

struct halfEdge
{
	int v, w; 
	halfEdge *next; 
}; 

halfEdge adj_pool[MaxNE], *adj[MaxNV], *adj_tail = adj_pool; 
#define trav(u) for (halfEdge *e = adj[u]; e; e = e->next)

int n, K; 
int maxsze = INF, G; 
int sze[MaxNV], son[MaxNV]; 
bool is_key[MaxNV]; 

int cnt, ans[MaxNV]; 

inline void addEdge(int u, int v, int w)
{
	adj_tail->v = v; 
	adj_tail->w = w; 
	adj_tail->next = adj[u]; 
	adj[u] = adj_tail++; 
}

inline void dfs_init(int u, int pre)
{
	sze[u] = is_key[u]; 
	son[u] = 0; 
	
	trav(u) if (e->v != pre)
	{
		dfs_init(e->v, u); 
		sze[u] += sze[e->v]; 
		
		relax(son[u], sze[e->v]); 
	}
	if (tense(maxsze, std::max(son[u], n - sze[u])))
		G = u; 
}

inline void dfs_push(int u, int pre)
{
	if (is_key[u]) ans[++cnt] = u; 
	trav(u) if (e->v != pre)
		dfs_push(e->v, u); 
}

int main()
{
//	freopen("A.in", "r", stdin); 
	
	read(n), read(K); 
	for (int i = 1; i < n; ++i)
	{
		int u, v, w; 
		read(u), read(v), read(w); 
		addEdge(u, v, w), addEdge(v, u, w); 
	}

	for (int i = 1; i <= K; ++i)
	{
		int x; read(x); 
		is_key[x] = true; 
	}
	
	dfs_init(1, 0); 
	dfs_push(G, 0); 
	
	K >>= 1; 
	for (int i = 1; i <= K; ++i)
		printf("%d %d\n", ans[i], ans[i + K]); 
	
	return 0; 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值