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