[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 \not = \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}\) 作为奶牛可以走的距离,即方便了旅馆间的合并,又方便了贪心的正确!

posted @ 2020-11-30 12:36  基地AI  阅读(172)  评论(0编辑  收藏  举报