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

  • 相关阅读:
    ubuntu输入法安装
    ffmpeg使用
    sourceforge无法登陆?没关系~~
    六大代码问题检验你的JAVA知识(转)
    关于Struts处理异常框架的小例子
    Spring Security连接数据库查询实例
    关于Struts的Token
    JAVA md5、SHA加密类
    利用commons upload+ffmpeg+mencoder完成视频的上传与转换
    初始化SSD1963
  • 原文地址:https://www.cnblogs.com/BaseAI/p/14060628.html
Copyright © 2020-2023  润新知