zoukankan      html  css  js  c++  java
  • 长链剖分学习笔记

    什么事长链剖分:

    • 对于每个节点令其子树高度最大的儿子的边为实边,其余边为虚边。
    • 于是树可以剖成由实边组成的若干长链。
    • 同重剖类似,有一个结论:对于任意节点 (u),其到根的路径的上的虚边数量(长链数量)是 (O(sqrt n)) 级别。

    CF1009F Dominant Indices

    对于每个节点 (u),求出一个最小的 (k),使得 (u) 子树中到 (u) 距离为 (k) 的点的数量最大。

    对于每个点 (f(u,d)) 表示 (u) 子树内到 (u) 的距离为 (d) 的节点个数。如果直接存储会炸裂。我们发现其实求 (u) 的信息完全可以直接利用一些在子节点求得的信息然后再往上增量。这点信息,不需要 copy,直接用指针即可。

    我们考虑对于每一个长链申请一个空间。从指针理解,(f_u) 表示 (f_u(d)) 数组的头指针。对于节点 (u),设它的实边儿子为 (v),则让 (f_v=f_u+1),即长成这样:

    然后对于每个节点,我们不仅从 (v) 上直接得到信息,还需要合并所有虚边连向的长链。这个普通转移即可。

    对于复杂度,每条长链最多被合并一次,而合并一次的复杂度为其链长,所以总复杂度为 (sum len=O(n))。而空间复杂度也一样,每条链所需空间为链长,同样得到 (O(n))

    对于分配内存,维护一个指针,指向一个数组的位置,然后将长 (dep+1) 的一段分配给它。

    内存分配还是开大一点好。

    https://codeforces.com/contest/1009/submission/133302570

    const int N=1e6+9;
    int n,h[N],son[N],g[N<<1],*f[N],ans[N],*cur=g;
    vi e[N];
    
    void dfs1(int u,int fa) {
    	for(auto v:e[u]) if(v!=fa) {
    		dfs1(v,u);
    		if(h[v]>=h[son[u]]) son[u]=v, h[u]=h[v]+1;
    	}
    }
    void dfs2(int u,int fa) {
    	if(son[u]) {
    		f[son[u]]=f[u]+1, dfs2(son[u],u), ans[u]=ans[son[u]]+1;
    	}
    	int mh=0;
    	for(auto v:e[u]) if(v!=fa&&v!=son[u]) {
    		f[v]=cur, cur+=h[v]+1;
    		dfs2(v,u);
    		rep(i,0,h[v]) f[u][i+1]+=f[v][i];
    		mh=max(mh,h[v]+1);
    	}
    	f[u][0]=1;
    	rep(i,0,mh)
    		if(f[u][i]>f[u][ans[u]]||f[u][i]==f[u][ans[u]]&&i<ans[u]) ans[u]=i;
    }
    
    int main() {
    	n=read();
    	rep(i,2,n) {
    		int u=read(), v=read();
    		e[u].emplace_back(v), e[v].emplace_back(u);
    	}
    	dfs1(1,0);
    	f[1]=cur, cur+=h[1]+1; dfs2(1,0);
    	rep(i,1,n) printf("%d
    ",ans[i]);
    	return 0;
    }
    

    经典题

    求树上长度(路径点数)恰好为 (k) 的路径数量。

    考虑在合并链的时候统计一下跨过 (u) 的链的数量即可。

    const int N=3e5+9;
    int n,k,ans,son[N],h[N],g[N<<1],*f[N],*cur=g;
    vi e[N];
    
    void dfs1(int u,int fa) {
    	for(auto v:e[u]) if(v!=fa) {
    		dfs1(v,u);
    		if(h[v]>=h[son[u]]) son[u]=v;
    	} h[u]=h[son[u]]+1;
    }
    void dfs2(int u,int fa) {
    	if(son[u]) {
    		f[son[u]]=f[u]+1, dfs2(son[u],u); if(h[u]>=k) ans+=f[u][k];
    	} f[u][0]++;
    	for(auto v:e[u]) if(v!=fa&&v!=son[u]) {
    		f[v]=cur, cur+=h[v]+1;
    		dfs2(v,u);
    		rep(i,0,min(k,h[v])) if(k-i-1<=h[u]) ans+=f[u][k-i-1]*f[v][i];
    		rep(i,0,h[v]) f[u][i+1]+=f[v][i];
    	}
    }
    z
    signed main() {
    	n=read(), k=read();
    	rep(i,2,n) {
    		int u=read(), v=read();
    		e[u].emplace_back(v), e[v].emplace_back(u);
    	}
    	dfs1(1,0), f[1]=cur, cur+=h[1]+1, dfs2(1,0);
    	printf("%lld
    ",ans);
    	return 0;
    }
    

    POI2014 Hotels

    求树上选三点两两距离相等的方案数。

    (f(u,i)) 表示子树中距离 (u)(i) 的节点个数。然后考虑一种特殊情况,即三个点 (x,y,z) (相互距离为 (d) 且总体 (lca=u))满足 (lca(y,z))(u) 的距离加上 (z)(u) 的距离等于 (d)(即 (lca(y,z))(z) 的链跨过 (u))。针对这种情况,我们设计状态 (g(u,i)) 表示存在多少这样的 (y,z) 满足到 (lca(y,z)) 距离相等(设之为 (d)),且 (lca(y,z))(u) 的距离为 (d-i)

    所以答案为(若 (u,v) 同时出现,此时意味着 (f(u)) 只是一个前缀,在和 (v) 合并时统计答案)

    [ans=g(u,0) +sum_i sum_{vin son_u} f(u,i-1) imes g(v,i)+f(u,i-1) imes g(v,i)\ g(u,i)=sum g(v,i+1)+f(u,i) imes f(v,i-1) ]

    长剖增量 DP 即可。

    const int N=4e5+9,mod=1e9+7;
    int n,*f[N],*g[N],h[N],p[N],*cur=p,son[N],ans;
    vi e[N];
    
    void dfs1(int u,int fa) {
    	for(auto v:e[u]) if(v!=fa) {
    		dfs1(v,u);
    		if(h[v]>=h[son[u]]) son[u]=v;
    	}
    	h[u]=h[son[u]]+1;
    }
    void dfs2(int u,int fa) {
    	if(son[u]) {
    		f[son[u]]=f[u]+1, g[son[u]]=g[u]-1;
    		dfs2(son[u],u); 
    	} f[u][0]=1; ans+=g[u][0];
    	for(auto v:e[u]) if(v!=fa&&v!=son[u]) {
    		f[v]=cur, cur+=h[v]*2, g[v]=cur, cur+=h[v]*2;
    		dfs2(v,u);
    		rep(i,0,h[v]-1) ans+=g[u][i+1]*f[v][i], ans%=mod;
    		rep(i,1,h[v]-1) ans+=f[u][i-1]*g[v][i], ans%=mod;
    		rep(i,0,h[v]-1) g[u][i+1]+=f[u][i+1]*f[v][i];
    		rep(i,1,h[v]-1) g[u][i-1]+=g[v][i]; 
    		rep(i,0,h[v]-1) f[u][i+1]+=f[v][i];
    	}
    }
    
    signed main() {
    	n=read();
    	rep(i,2,n) {
    		int u=read(), v=read();
    		e[u].emplace_back(v), e[v].emplace_back(u);
    	}
    	dfs1(1,0), f[1]=cur, cur+=h[1]*2, g[1]=cur, cur+=h[1]*2, dfs2(1,0);
    	printf("%lld
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    PHP chr函数 对应的AscII码
    微信小程序:样式,事件
    PHP 实现移动端极光推送(转)
    微信小程序服务器请求和上传数据,上传图片并展示,提交表单完整实例代码附效果图(转)
    大头照上传预览,并操作数据库和删除文件夹中存储的之前的图片;$_SERVER['DOCUMENT_ROOT']上传图片和删除图片的时候不要用绝对路径,可以用这个路径
    thinkphp 条件搜索分页(tp自带Page类)
    asp搭配环境
    html5手机端手指滑动选项卡滚动切换效果(转)
    tp框架实现ajax注册验证
    tp框架链接数据库的基本操作
  • 原文地址:https://www.cnblogs.com/TetrisCandy/p/15476318.html
Copyright © 2011-2022 走看看