zoukankan      html  css  js  c++  java
  • bzoj3572 世界树(虚树,倍增)

    题目链接

    解题思路

      题目的问法很明显是虚树题的问法。根据询问点建虚树,因为虚树不止有询问的点,还有他们的lca,所以先对建出来的虚树预处理出来虚树上每个点离他最近的议事处的距离和点的编号(自己就是议事处肯定就是自己,主要是对增加的lca进行处理)。因为最近的点可能来自下面的点也可能是上面的点,所以跑两次dfs从两个方向更新一下即可。
      对于虚树的一条边来说,两个点都有其对应的议事处,它们之间的边代表原树上的一些点(或者说子树)。对于每条边我们都计算一下两个点对应的议事处的分界点(注意不是这个点本身,以为它可能不是议事处而是新增的lca)即可。方法是用倍增从深度最大的那个点往上跳,计算跳到的那个点当分界点时到两个议事处的距离,注意还要考虑距离相等编号选最小的议事处。
      还有一个细节需要注意,对于点u和点v(u是v的祖先),我们计算分界点d分开的点的数量不能直接加上sz[u]-sz[d],因为u有好几个子树,这样加肯定会重复,可以算出u的儿子son,满足son是v的祖先,这样每次减去sz[son],加上sz[son]-sz[d],最后再加上sz[u]就行了。

    代码

    const int maxn = 6e5+10;
    struct E {
    	int to, nxt;
    } e[maxn];
    int h[maxn], tot;
    void add(int u, int v) {
    	e[++tot] = {v, h[u]};
    	h[u] = tot;
    }
    int ldfn[maxn], rdfn[maxn], tim;
    int dep[maxn], f[maxn][20], sz[maxn];
    void dfs(int u, int p) {
        //cout << "!" << u << endl;
    	ldfn[u] = ++tim; sz[u] = 1;
    	for (int i = h[u]; i; i=e[i].nxt) {
    		int v = e[i].to;
    		if (v==p) continue;
    		dep[v] = dep[u]+1;
    		f[v][0] = u;
    		for (int j = 1; j<20; ++j) f[v][j] = f[f[v][j-1]][j-1];
    		dfs(v, u);
    		sz[u] += sz[v];
    	}
    	rdfn[u] = tim;
    }
    int lca(int u, int v) {
    	if (dep[u]<dep[v]) swap(u, v);
    	for (int i = 19; i>=0; --i)
    		if (dep[f[u][i]]>=dep[v]) u = f[u][i];
    	if (u==v) return u;
    	for (int i = 19; i>=0; --i)
    		if (f[u][i]!=f[v][i]) u = f[u][i], v = f[v][i];
    	return f[u][0];
    }
    int n, m, k, d[maxn], sk[maxn], vis[maxn];
    void build() {
        tot = 0; //!!!!
    	sort(d+1, d+k+1, [](int a, int b) {return ldfn[a]<ldfn[b];});
        //for (int i = 1; i<=k; ++i) cout << d[i] << (i==k ? '
    ':' ');
    	int keynum = k;
    	for (int i = 1; i<keynum; ++i) d[++k] = lca(d[i], d[i+1]);
    	sort(d+1, d+k+1, [](int a, int b) {return ldfn[a]<ldfn[b];});
    	k = unique(d+1, d+k+1)-d-1;
    	int tp = 1; sk[tp] = d[1];
    	for (int i = 2; i<=k; ++i) {
    		while(tp && rdfn[sk[tp]]<ldfn[d[i]]) --tp; 
    		if (tp) add(sk[tp], d[i]);
    		sk[++tp] = d[i];
    	}
    }
    int dp[maxn], g[maxn];
    void dfs1(int u, int p) {
    	dp[u] = INF;
    	for (int i = h[u]; i; i=e[i].nxt) {
    		int v = e[i].to;
    		if (v==p) continue;
            dfs1(v, u);
    		int dis = dep[v]-dep[u]; //cout << dis << endl; //注意相同距离取编号小的
    		if (dp[v]+dis<dp[u] || (dp[v]+dis==dp[u]&&g[v]<g[u])) dp[u] = dp[v]+dis, g[u] = g[v];
            //cout << "!" << u << ' ' << v << ' ' << g[u] << ' ' << dp[u] << endl;
    	}
    	if (vis[u]) dp[u] = 0, g[u] = u;
    }
    void dfs2(int u, int p) {
    	for (int i = h[u]; i; i=e[i].nxt) {
    		int v = e[i].to;
    		if (v==p) continue;
    		int dis = dep[v]-dep[u]; //cout << dis << endl; //注意相同距离取编号小的
    		if (dp[u]+dis<dp[v] || (dp[u]+dis==dp[v]&&g[u]<g[v])) dp[v] = dp[u]+dis, g[v] = g[u];
            dfs2(v, u);
    	}
    }
    int ans[maxn], tmp[maxn];
    void calc(int u, int v) {
    	int son = v;
    	for (int i = 19; i>=0; --i)
    		if (dep[f[son][i]]>dep[u]) son = f[son][i];
    	ans[g[u]] -= sz[son]; //先减去儿子的贡献
        //cout << ans[g[u]] << endl;
    	int d = v;
    	for (int i = 19; i>=0; --i) {
    		int t = f[d][i];
    		if (dep[t]<dep[u]) continue; //倍增往上找分界点
    		int l = dp[v]+dep[v]-dep[t], r = dp[u]+dep[t]-dep[u];
    		if (l<r || (l==r && g[v]<g[u])) d = t; //相同距离取最小
    	}
        //cout << u << ' ' << g[u] << "|||" << v << ' ' << g[v] << "!!" << d << endl;
    	ans[g[u]] += sz[son]-sz[d]; ans[g[v]] += sz[d]-sz[v];
        //cout << ans[g[u]] << ' ' << ans[g[v]] << endl;
    }
    void dfs3(int u, int p) {
    	for (int i = h[u]; i; i=e[i].nxt) {
    		int v = e[i].to;
    		if (v==p) continue;
    		calc(u, v);
            dfs3(v, u);
    	}
    	ans[g[u]] += sz[u];
    }
    void init() {
        tot = 0;
        for (int i = 1; i<=k; ++i) h[d[i]] = ans[d[i]] = g[d[i]] = vis[d[i]] = 0, dp[d[i]]= INF;
    }
    int main() {
    	cin >> n;
    	for (int i = 1, a, b; i<n; ++i) {
    		scanf("%d%d", &a, &b);
    		add(a, b);
    		add(b, a);
    	}
        dep[1] = 1; dfs(1, 0);
        //for (int i = 1; i<=n; ++i) cout << ldfn[i] << ' ' << rdfn[i] << endl;
        clr(h, 0); tot = 0; //清空原树
    	cin >> m;
    	while(m--) {
    		scanf("%d", &k); int tk = k;
    		for (int i = 1; i<=k; ++i) scanf("%d", &d[i]), tmp[i] = d[i];
    		for (int i = 1; i<=k; ++i) vis[d[i]] = 1;
    		if (!vis[1]) d[++k] = 1;
    		build();
    		//for (int i = 1; i<=k; ++i) printf(i==k ? "%d
    ":"%d ", d[i]);
    		dfs1(1, 0); 
            //for (int i = 1; i<=k; ++i) cout << d[i] << ' ' << g[d[i]] << endl;
            dfs2(1, 0); dfs3(1, 0);
            //for (int i = 1; i<=k; ++i) cout << d[i] << ' ' << g[d[i]] << endl;
            for (int i = 1; i<=tk; ++i) printf(i==tk ? "%d
    ":"%d ", ans[tmp[i]]);
            init();
    	}
    	return 0;
    }
    
  • 相关阅读:
    【剑指Offer-分解让复杂问题简单化】面试题38:字符串的排列
    【剑指Offer-分解让复杂问题简单化】面试题37:序列化二叉树
    【剑指Offer-分解让复杂问题简单化】面试题35:复杂链表的复制
    【剑指Offer-举例让抽象问题具体化】面试题34:二叉树中和为某一值的路径
    Git操作时遇到的一些问题和相应的处理方式
    Git分支规范说明
    敏捷的原则和价值观
    MongoDB批量更新和批量插入的方式
    请求地址中含有特殊字符#时如何有效处理
    IntelliJ IDEA Ultimate 6.2 版本免费试用期过期后如何破解
  • 原文地址:https://www.cnblogs.com/shuitiangong/p/15426528.html
Copyright © 2011-2022 走看看