[CF] 1307F Cow and Vacation(思维/贪心)

题目

题目地址

题解

记每个旅馆为 (rest_i,1 le i le r)

奶牛从 (a)(b) 的路径可以分为两种:一种是直接 (a->b);另一种是中间经过若干((c) 间)旅馆 (a -> rest_{s_1}->...-> rest_{s_c} -> b)。其中一个点到另一个点的路径长度不超过 (k)

换言之如果 (rest_i) 可以到 (rest_j),且 (a) 可以到 (rest_i)(b) 可以到 (rest_j)(都不超过 (k) 步),那么 (a) 可以到 (b)

这启发我们如果两间旅馆 (rest_i) 可以到 (rest_j),那么我们可以把这两间旅馆合并。最后,每个“旅馆连通块”内的旅馆可以互相通

但是这个算法的瓶颈在于,我们每次要搜索分别从 (a,b) 出发蓑鲉可到达“旅馆连通块”集合 (A,B),再判断是否有相同的“旅馆联通块”(即 (A cap B ot = emptyset))。

考虑用贪心来优化这个算法。因为如果最后的路径是 (a -> rest_{s_1}->...-> rest_{s_c} -> b),我们直接从 (a)(b) 的方向走 (k) 步到 (go_a)(b)(a) 的方向走 (k) 步到 (go_b),看 (go_a)(go_b) 是否在同一个“旅馆连通块”中。很容易可以发现这样贪心是错误的,因为走了 (k) 步之后还不一定可以走到恰好是旅馆的点。因此可能会把一些可行的路径判为不可行(不可行的路径一定会判为不可行)。

我们来分析一下这样贪心为什么是错误的:

首先做一步转化,我们可以假设奶牛走 (x) 步就要休息,这样的话对于每个旅馆( (rest_i,1 le i le r)),向外面走 (k-x) 步的地方都可以称为“旅馆”,我们把 (rest_i) 为中心,向外辐射 (k-x) 步的“旅馆”集合记做 (S_i)。原来的 “(rest_i)(k) 步内可以到 (rest_j)” 就可以描述为 “(S_i) 中的某个点在 (2x-k) 步内可以到 (S_j) 中的某个点”(简记为 (S_i) 可以到 (S_j)

(2x-k) 可以结合 (S_i) 的意义推出,具体的是由 (k-2(k-x)) 得到的。由此可以发现 (x) 有意义的范围是 (frac{k}{2} le x le k)。)

贪心错误的原因在于,(S_i) 可以到 (S_j),中间经过了一段长度 (le 2x-k) 的不是旅馆的路径。 我们贪心 (a) 直接往 (b)(x) 步(注意我们在讨论转化后,已经不是走 (k) 步) ,最后可能落在这段 长度 (le 2x-k) 的不是旅馆的路径上。

这启发我们令 (2x-k=0),即 (x = frac{k}{2}),则贪心 (a) 直接往 (b)(x) 步后,不会落在不是旅馆的路径上。这样,我们就可以用贪心优化掉这个算法的瓶颈。

我们可以在每条边中间加一个点,这样原条件就变为“奶牛走 (2k) 步就要休息”,于是解决了 (k) 不为偶数的问题。

合并旅馆联通块的过程,我们可以以 (rest_i,1 le i le r) 为起点同时出发跑 (k) 步,将可达的旅馆联通块合并。

时间复杂度 (O((n+q) log n))

代码

Talk is cheap.Show me the code.

#include<bits/stdc++.h>
#define mp make_pair
#define fi first
#define se second
using namespace std;
inline int read() {
    int x = 0, f = 1; char ch = getchar();
    while(ch<'0' || ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<3)+(x<<1)+(ch^48); ch=getchar(); }
    return x * f;
}
typedef pair<int,int> PII;
const int N = 4e5+7;
int n,k,r,cnt,tot;
int head[N],rest[N];
bool fg[N];
struct Edge {
	int next,to;
}edge[N<<1];
inline void add(int u,int v) {
	edge[++cnt] = (Edge)<%head[u],v%>;
	head[u] = cnt;
}
namespace UF {
	int fa[N];
	void Init(int n) {
		for(int i=1;i<=n;++i) fa[i] = i;
	}
	int Find(int x) {
		return (x==fa[x] ? x : fa[x]=Find(fa[x]));
	}
	void Join(int x,int y) {
		int fx = Find(x), fy = Find(y);
		if(fx != fy) {
			fa[fx] = fy;
		}
	}
}
bool vis[N];
void Bfs() {
	queue<PII> q;
	for(int i=1;i<=r;++i) {
		q.push(mp(rest[i],0)); vis[rest[i]] = 1;
	}
	while(!q.empty()) {
		int u = q.front().fi, step = q.front().se;
		q.pop();
		if(step >= k) break;
		for(int i=head[u];i;i=edge[i].next) {
			int v = edge[i].to;
			UF::Join(u,v);
			if(!vis[v]) {
				vis[v] = 1;
				q.push(mp(v,step+1));
			}
		}
	}
}
const int lgN = 20;
int fa[N][lgN],dep[N],lg[N];
void Init() {
	for(int i=2;i<N;++i) lg[i] = lg[i>>1] + 1;
}
void Dfs1(int u,int fath) {
	fa[u][0] = fath; dep[u] = dep[fath] + 1;
	for(int i=1;i<=lg[dep[u]];++i)
		fa[u][i] = fa[fa[u][i-1]][i-1];
	for(int i=head[u];i;i=edge[i].next) {
		int v = edge[i].to;
		if(v != fath) {
			Dfs1(v,u);
		}
	}
}
int LCA(int x,int y) {
	if(dep[x] < dep[y]) swap(x,y);
	while(dep[x] > dep[y]) x = fa[x][lg[dep[x]-dep[y]]];
	if(x == y) return x;
	for(int i=lg[dep[x]];i>=0;--i)
		if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
	return fa[x][0];
}
int walk(int x,int y,int lca) {
	int res = -1, now = -1;
	if(k <= dep[x]-dep[lca]) {
		res = x;
		now = k;
	} else {
		res = y;
		now = (dep[y]-dep[lca]) - (k-(dep[x]-dep[lca]));
	}
	int up = lg[now];
	for(int i=up;i>=0;--i) {
		if(now >= (1<<i)) {
			res = fa[res][i];
			now -= (1<<i);
		}
	}
	return res;
}
int main()
{
	n = read(), k = read(), r = read();
	tot = n;
	for(int i=1;i<n;++i) {
		int u = read(), v = read();
		++tot;
		add(u,tot), add(tot,u);
		add(v,tot), add(tot,v);
	}
	for(int i=1;i<=r;++i) {
		int x = read();
		fg[x] = 1;
		rest[i] = x;
	}
	UF::Init(tot);
	Bfs();
	Init();
	Dfs1(1,0);
	int Q = read();
	while(Q--) {
		int x = read(), y = read();
		int lca = LCA(x,y);
		int gox = walk(x,y,lca), goy = walk(y,x,lca);
		if((dep[x]+dep[y]-2*dep[lca]<=2*k) || (UF::Find(gox)==UF::Find(goy))) puts("YES");
		else puts("NO");
	}
	return 0;
}
/*
8 3 3
1 2
2 3
3 4
4 5
4 6
6 7
7 8
2 5 8
2
7 1
8 1

YES
NO
*/

总结

这个性质非常强,用 (frac{k}{2}) 作为奶牛可以走的距离,即方便了旅馆间的合并,又方便了贪心的正确!

原文地址:https://www.cnblogs.com/BaseAI/p/14060628.html