zoukankan      html  css  js  c++  java
  • 长链剖分总结

    长链剖分

    长链剖分是一种类似(mbox{dsu on tree})的小(mbox{trick}),可以把维护子树中只与深度有关的信息做到线性的时间复杂度。

    实现方式&复杂度证明

    对每个点寻找深度最大的儿子作为重儿子,其余作为轻儿子。由此得到了若干条互不相交的长链。
    在维护信息的过程中,先(O(1))继承重儿子的信息,再暴力合并其余轻儿子的信息。
    因为每个点仅属于一条长链,且一条长链只会在链顶位置作为轻儿子暴力合并一次,所以时间复杂度线性。
    (O(1))继承重儿子信息这点上有不同的实现方式,一个巧妙的方法是利用指针实现,具体可以参见代码。

    一些简单的题目

    codeforces1009F
    给你一棵树,定义(d_{x,i})表示(x)子树内和(x)距离为(i)的节点数,对每个(x)求使(d_{x,i})最大的(i),如有多个输出最小的。

    裸题,没什么好讲的,看代码吧。

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    int gi(){
    	int x=0,w=1;char ch=getchar();
    	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    	if (ch=='-') w=0,ch=getchar();
    	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    	return w?x:-x;
    }
    const int N = 1e6+5;
    int n,to[N<<1],nxt[N<<1],head[N],cnt;
    int len[N],son[N],tmp[N],*f[N],*id=tmp,ans[N];
    void link(int u,int v){
    	to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
    	to[++cnt]=u;nxt[cnt]=head[v];head[v]=cnt;
    }
    void dfs(int u,int ff){
    	for (int e=head[u];e;e=nxt[e])
    		if (to[e]!=ff){
    			dfs(to[e],u);
    			if (len[to[e]]>len[son[u]]) son[u]=to[e];
    		}
    	len[u]=len[son[u]]+1;
    }
    void dp(int u,int ff){
    	f[u][0]=1;
    	if (son[u]) f[son[u]]=f[u]+1,dp(son[u],u),ans[u]=ans[son[u]]+1;
    	for (int e=head[u];e;e=nxt[e]){
    		int v=to[e];if (v==ff||v==son[u]) continue;
    		f[v]=id;id+=len[v];dp(v,u);
    		for (int j=1;j<=len[v];++j){
    			f[u][j]+=f[v][j-1];
    			if ((j<ans[u]&&f[u][j]>=f[u][ans[u]])||(j>ans[u]&&f[u][j]>f[u][ans[u]]))
    				ans[u]=j;
    		}
    	}
    	if (f[u][ans[u]]==1) ans[u]=0;
    }
    int main(){
    	n=gi();
    	for (int i=1;i<n;++i) link(gi(),gi());
    	dfs(1,0);f[1]=id;id+=len[1];
    	dp(1,0);
    	for (int i=1;i<=n;++i) printf("%d
    ",ans[i]);
    	return 0;
    }
    

    [cogs2652]秘术「天文密葬法」
    给你一棵树,每个点有两个权值(a_i,b_i),你需要找出一条长为(m)的路径,最小化(frac{sum a_i}{sum b_i})

    很明显的分数规划。先二分一个(mid),于是原问题转化为判定性问题:是否存在一条长为(m)的路径,使得(sum a_i-midsum b_i<0)
    那也就是说我们要找一条长为(m)且权值和最小的路径。这里在(O(1))继承重儿子的时候需要给整个数组加上一个值,实现方式就是对每个点开个变量表示加了多少。

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    int gi(){
    	int x=0,w=1;char ch=getchar();
    	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    	if (ch=='-') w=0,ch=getchar();
    	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    	return w?x:-x;
    }
    const int N = 2e5+5;
    int n,m,a[N],b[N],to[N<<1],nxt[N<<1],head[N],cnt,len[N],son[N];
    double val[N],tmp[N],*f[N],*id=tmp,ans=1e18;
    void link(int u,int v){
    	to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
    	to[++cnt]=u;nxt[cnt]=head[v];head[v]=cnt;
    }
    void dfs(int u,int ff){
    	for (int e=head[u];e;e=nxt[e])
    		if (to[e]!=ff){
    			dfs(to[e],u);
    			if (len[to[e]]>len[son[u]]) son[u]=to[e];
    		}
    	len[u]=len[son[u]]+1;
    }
    void dp(int u,int ff,double mid){
    	val[u]=a[u]-mid*b[u];f[u][0]=0;
    	if (son[u]) f[son[u]]=f[u]+1,dp(son[u],u,mid),val[u]+=val[son[u]],f[u][0]-=val[son[u]];
    	for (int e=head[u];e;e=nxt[e]){
    		int v=to[e];if (v==ff||v==son[u]) continue;
    		f[v]=id;id+=len[v];dp(v,u,mid);
    		for (int j=0;j<len[v]&&j<m;++j)
    			if (m-j-1<len[u]) ans=min(ans,f[v][j]+val[v]+f[u][m-j-1]+val[u]);
    		for (int j=0;j<len[v]&&j<m;++j)
    			f[u][j+1]=min(f[u][j+1],f[v][j]+val[v]-val[u]+a[u]-mid*b[u]);
    	}
    	if (m<len[u]) ans=min(ans,f[u][m]+val[u]);
    }
    int main(){
    	freopen("cdcq_b.in","r",stdin);
    	freopen("cdcq_b.out","w",stdout);
    	n=gi();m=gi()-1;
    	for (int i=1;i<=n;++i) a[i]=gi();
    	for (int i=1;i<=n;++i) b[i]=gi();
    	for (int i=1;i<=n;++i) ans=min(ans,1.0*a[i]/b[i]);
    	if (m==-2||!m) return printf("%.2lf
    ",ans),0;
    	for (int i=1;i<n;++i) link(gi(),gi());
    	dfs(1,0);
    	double l=0,r=N;
    	while (r-l>1e-3){
    		double mid=(l+r)/2;
    		memset(tmp,0x7f,sizeof(tmp));ans=1e18;
    		id=tmp;f[1]=id;id+=len[1];dp(1,0,mid);
    		if (ans>=0) l=mid;else r=mid;
    	}
    	if (l>=200000) puts("-1");
    	else printf("%.2lf
    ",l);
    	return 0;
    }
    

    [BZOJ4543][POI2014]Hotel加强版
    给你一棵树,从中选(3)个点,两两距离相等,求方案数。

    考虑暴力(dp)。设(f_{i,j})表示(i)子树内与(i)距离为(j)的点的个数,(g_{i,j})表示(i)子树内,满足第三个点和(i)的距离为(j)的点对数目。
    这样每次可以拿(f_{u,j} imes g_{v,j+1})(g_{u,j+1} imes f_{v,j})更新答案,拿(f_{u,j} imes f_{v,j-1})更新(g_{u,j})(f_{v,j})更新(f_{u,j+1})(g_{v,j})更新(g_{u,j-1})
    仔细观察会发现,这个(g)的更新是反过来的。
    所以(g)数组就反着开就行了,为了避免出错可以多开点空间。

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    int gi(){
    	int x=0,w=1;char ch=getchar();
    	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    	if (ch=='-') w=0,ch=getchar();
    	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    	return w?x:-x;
    }
    #define ll long long
    const int N = 1e5+5;
    int n,to[N<<1],nxt[N<<1],head[N],cnt,len[N],son[N];
    ll tmp[N<<2],*f[N],*g[N],*id=tmp,ans;
    void link(int u,int v){
    	to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
    	to[++cnt]=u;nxt[cnt]=head[v];head[v]=cnt;
    }
    void dfs(int u,int ff){
    	for (int e=head[u];e;e=nxt[e])
    		if (to[e]!=ff){
    			dfs(to[e],u);
    			if (len[to[e]]>len[son[u]]) son[u]=to[e];
    		}
    	len[u]=len[son[u]]+1;
    }
    void dp(int u,int ff){
    	if (son[u]) f[son[u]]=f[u]+1,g[son[u]]=g[u]-1,dp(son[u],u);
    	f[u][0]=1;ans+=g[u][0];
    	for (int e=head[u];e;e=nxt[e]){
    		int v=to[e];if (v==ff||v==son[u]) continue;
    		f[v]=id;id+=len[v]<<1;g[v]=id;id+=len[v]<<1;dp(v,u);
    		for (int j=0;j<len[v];++j){
    			if (j) ans+=f[u][j-1]*g[v][j];
    			ans+=g[u][j+1]*f[v][j];
    		}
    		for (int j=0;j<len[v];++j){
    			g[u][j+1]+=f[u][j+1]*f[v][j];
    			if (j) g[u][j-1]+=g[v][j];
    			f[u][j+1]+=f[v][j];
    		}
    	}
    }
    int main(){
    	n=gi();
    	for (int i=1;i<n;++i) link(gi(),gi());
    	dfs(1,0);
    	f[1]=id;id+=len[1]<<1;g[1]=id;id+=len[1]<<1;dp(1,0);
    	printf("%lld
    ",ans);return 0;
    }
    

    咕咕咕?

  • 相关阅读:
    【Spring Boot】关于上传文件例子的剖析
    GIT初始学习记录
    Java代码混淆工具ProGuard
    Kafka 概念、单机搭建与使用
    流网络分析系统-SNAS
    【Spring Boot】使用JDBC 获取相关的数据
    二叉树【按层打印、序列化、反序列化】
    跨域共享cookie
    启动kafka集群,关闭kafka集群脚本
    kafka-consumer.properties
  • 原文地址:https://www.cnblogs.com/zhoushuyu/p/9468669.html
Copyright © 2011-2022 走看看