zoukankan      html  css  js  c++  java
  • BZOJ 3653: 谈笑风生(离线, 长链剖分, 后缀和)

    题意

    给你一颗有 (n) 个点并且以 (1) 为根的树。共有 (q) 次询问,每次询问两个参数 (p, k) 。询问有多少对点 ((p, a, b)) 满足 (p,a,b) 为三个不同的点,(p, a) 都为 (b) 的祖先,且 (p)(a) 的距离不能超过 (k)

    (nle 300000 , qle 300000) 不要求强制在线。

    题解

    (dep[u]) 为点 (u) 的深度,(sz[u])(u) 的子树大小(除去 (u) 本身)

    首先我们考虑两种情况:

    1. (a)(p) 的祖先,那么这部分贡献很好计算,就是 (min{dep[p] - 1,k} imes sz[u])
    2. (a)(p) 的子树内,那么这部分贡献就是 (displaystyle sum_{dis(p,a) le k} sz[a])

    我们现在只要考虑第二部分贡献怎么求。

    不难发现,这些点的深度就是 ([dep[p], dep[p]+k]) 这个范围内的。

    那么我们可以对于每个点用个 主席树 来存储这些信息,可以在线回答询问。

    那么离线的话,可以考虑用 线段树合并 维护它每个子树的信息。

    具体来说,这些都是对于每个 (dep) 维护它的 (sz) 的和,然后查区间和就行了。

    然而这些时空复杂度都是 (O(n log n)) ,其实还有更好的做法。

    为什么我发现了呢qwq?

    因为 fatesky 做这道题线段树合并做法的时候,Wearry 说可以 长链剖分 那就是 (O(n)) 的啦。

    我们令 (displaystyle maxdep[u]=max_{v in child[u]} {dep[v}) 也就是它子树中的最大深度。

    具体来说,长链剖分就是把每个点儿子中 (maxdep) 最大的那个当做重儿子。重儿子与父亲连的边叫做重边。一连串重边不间断连到一起就叫做重链。

    然后我们就有一条性质。

    性质1 : 重链长度之和是 (O(n)) 的。

    这个很显然啦,因为总共只有 (O(n)) 级别的边。

    有了这个我们就可以解决一系列 关于深度的动态规划 问题了,对于这列问题常常都可以做到 (O(n)) 的复杂度。

    具体操作就是,每次暴力继承重儿子的 (dp) 状态,然后轻儿子暴力合并上去。

    不难发现这个复杂度是 (O(sum) 重链长 ()) (= O(n)) 的。

    继承的时候常常需要移位,并且把当前节点贡献算入,并且这个 (dp) 需要动态空间才能实现。

    对于这道题我们考虑维护一个后缀和,也就是对于 (u) 子树中的 (v)(dep[v] ge k) 的所有 (sz[v]) 的和。

    不难发现后缀和是很好合并的,这个的复杂度只需要 (O(min maxdep[v]))

    每次添加一个点 (sz[u]) 对于 (dep[u]) 的贡献只会对一个点的贡献产生影响,这个复杂度是 (O(1)) 的。

    代码实现的话,就可以用一个 std :: vector ,按深度从大到小 ( (maxdep[u] o dep[u]) )存储每个点的信息,因为这样最方便继承重儿子状态(每次加入状态只在整个 vector 末端添加一个元素)

    其实可以动态开内存,顺着做,但我似乎学不来

    常数似乎有点大,没比 (O(n log n)) 快多少,vector 用多了... Wearry 到是优化了点常数到了 (4000+ ms)

    话说这个很像原来 DOFY 讲过的那道 Dsu on Tree

    代码

    #include <bits/stdc++.h>
    
    #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
    #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
    #define Set(a, v) memset(a, v, sizeof(a))
    #define Cpy(a, b) memcpy(a, b, sizeof(a))
    #define debug(x) cout << #x << ": " << x << endl
    #define DEBUG(...) fprintf(stderr, __VA_ARGS__)
    
    using namespace std;
    
    typedef long long ll;
    
    inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
    inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;}
    
    inline int read() {
    	int x = 0, fh = 1; char ch = getchar();
    	for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
    	for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
    	return x * fh;
    }
    
    void File() {
    #ifdef zjp_shadow
    	freopen ("3653.in", "r", stdin);
    	freopen ("3653.out", "w", stdout);
    #endif
    }
    
    const int N = 3e5 + 1e3;
    
    struct Ask { int k, id; } ; vector<Ask> V[N];
    
    vector<int> G[N]; int sz[N], maxdep[N], dep[N], sonmaxdep[N], son[N], rt[N];
    
    vector<ll> sum[N]; int n, q; ll ans[N], Size = 0;
    
    void Dfs_Init(int u, int fa = 0) {
    
    	maxdep[u] = dep[u] = dep[fa] + 1;
    
    	For (i, 0, G[u].size() - 1) {
    		register int v = G[u][i];
    		if (v ^ fa) Dfs_Init(v, u), chkmax(maxdep[u], maxdep[v]);
    	}
    
    }
    
    void Dfs(int u, int fa = 0) {
    
    	For (i, 0, G[u].size() - 1) {
    		int v = G[u][i];
    		if (v == fa) continue ;
    		Dfs(v, u); sz[u] += sz[v];
    		if (maxdep[v] > maxdep[son[u]]) son[u] = v;
    	}
    	rt[u] = rt[son[u]]; if (!rt[u]) rt[u] = ++ Size;
    
    	int len = (int)sum[rt[u]].size();
    	ll Last = len ? sum[rt[u]][len - 1] : 0;
    	sum[rt[u]].push_back(Last);
    
    	if (son[u]) {
    		For (i, 0, G[u].size() - 1) {
    			int v = G[u][i]; if (v == fa || v == son[u]) continue ;
    			For (j, 0, sum[rt[v]].size() - 1) {
    				int nowdep = (maxdep[son[u]] - maxdep[v]) + j;
    				sum[rt[u]][nowdep] += sum[rt[v]][j];
    			}
    			sum[rt[u]][len] += sum[rt[v]][sum[rt[v]].size() - 1];
    		}
    	}
    
    	For (i, 0, V[u].size() - 1) {
    		Ask now = V[u][i];
    		ans[now.id] = sum[rt[u]][len];
    		if (len > now.k) ans[now.id] -= sum[rt[u]][len - now.k - 1];
    		ans[now.id] += 1ll * min(dep[u] - 1, now.k) * sz[u];
    	}
    
    	sum[rt[u]][len] += sz[u]; ++ sz[u];
    
    }
    
    int main () {
    
    	File();
    
    	n = read(); q = read();
    
    	For (i, 1, n - 1) {
    		int u = read(), v = read();
    		G[u].push_back(v);
    		G[v].push_back(u);
    	}
    
    	For (i, 1, q) {
    		int p = read(), k = read();
    		V[p].push_back((Ask) {k, i});
    	}
    
    	Dfs_Init(1); Dfs(1);
    
    	For (i, 1, q)
    		printf ("%lld
    ", ans[i]);
    
    	return 0;
    }
    
  • 相关阅读:
    参考选择屏幕(控制选择屏幕两个屏幕,单值输入……通过函数实现单值输入)
    json串转化成xml文件、xml文件转换成json串
    创建xml文件、解析xml文件
    CDATA(不应由XML解析器进行解析的文本数据)、CDATA的使用场景
    python添加、修改、删除、访问类对象属性的2种方法
    类对象序列化为json串,json串反序列化为类对象
    python对象转化为json串、json串转化为python串
    windows下安装Mysql—图文详解
    用列表实现一个简单的图书管理系统 python
    列表去重几种方法 python
  • 原文地址:https://www.cnblogs.com/zjp-shadow/p/9262234.html
Copyright © 2011-2022 走看看