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

  • 相关阅读:
    Longest Palindromic Substring
    PayPal MLSE job description
    Continuous Median
    Remove Duplicates From Linked List
    Valid IP Address
    Longest substring without duplication
    Largest range
    Subarray sort
    Multi String Search
    Suffix Trie Construction
  • 原文地址:https://www.cnblogs.com/BaseAI/p/14060628.html
Copyright © 2011-2022 走看看