换根Dp

换根Dp

模板题

什么是换根Dp捏?换根Dp就是根据父亲的属性来更新他更新儿子的属性,相当于只需要考虑把顶点移到儿子对结果造成的影响。

奶牛聚会

每个奶牛居住在 N 个农场中的一个这些农场由 N-1条道路连接,并且从任意一个农场都能够到达另外一个农场。同时给一个C数组表示每个农场的牛的数量,每条道路有个长度。问:在哪里开聚会他们其他的奶牛来的距离和最短。n<=1e5.

套路:先求出以1号点为终点的情况,然后考虑把终点移动,比如下图中把终点移动到2上去的话,红色区域(n-sz[2])的要走的距离会增加(w(1-2)),蓝色区域(sz[2])的距离会减少(w(1-2)).然后就一步步转移即可

const int N=1e5+1000,mod=1e9+7;
int lowbit(int x){return x&-x;}
int gcd(int a,int b){return a%b==0?b:gcd(b,a%b);}

int n;
int sz[N];//
int w[N],d[N];
int de[N];
vector<pi>v[N];
int sm;

void dfs1(int u,int pr)//求出sz 和 以为1为终点的情况
{
	sz[u]=w[u];//w是当前点的牛子个数
	d[u]=0;//d初始话为0
	for(pi t2:v[u])
	{
		int j=t2.x,y=t2.y;
		if(j==pr)continue;
		de[j]=de[u]+y;//de 表示j到1 的距离
		dfs1(j,u);//递归下去
		sz[u]+=sz[j];//
		d[u]+=d[j]+w[j]*de[j];//这个是权值的计算,当前牛子个数乘以要走的距离
	}
	
	
}

void dfs2(int u,int pr)
{
	for(pi t2:v[u])
	{
		int j=t2.x,y=t2.y;
		if(j==pr)continue;
        //有变化的情况就是  (n-sz[j])*y - sz[j]*y  n代表牛的总数。
		d[j]=y*(sm-2*sz[j])+d[u];//
		dfs2(j,u);//记住 是先处理再递归!
	}
}

void solve()
{
	cin>>n;
	for(int i=1;i<=n;i++)cin>>w[i],sm+=w[i];
	for(int i=1;i<n;i++)
	{
		int a,b,c;cin>>a>>b>>c;
		v[a].ps({b,c});
		v[b].ps({a,c});
	}
	dfs1(1,0);
	// cout<<d[1]<<endl;
	dfs2(1,0);
	int res=d[1];

	for(int i=2;i<=n;i++)res=min(res,d[i]);//最后去一个min即可。
	cout<<res<<endl;
}

signed main()
{
	kd;
	int _;_=1;
	//cin>>_;
	while(_--)solve();	
	return 0;
}

拓展一

Nearby Cows G---换根+容斥

问:每个点的和他相差不大于等于k的权值和,k<=20,n<=1e5.,

d[i][j]表示i号点和他相差k的权值和。那么跟上面一样考虑如果我把当前考虑的点移到儿子,那么就需要考虑儿子的子树到儿子的情况,也就是儿子的子数到v的距离等于到u的距离-1,推出:d[v][k]+=d[u][k1].但是要注意可能d[u][k2]会和d[v][k1]重复计算,所以再处理之前要提前逆着减去比自己小2的数量。

typedef pair<int,int>pi;
const int N=1e5+100,mod=1e9+7;
int lowbit(int x){return x&-x;}
int gcd(int a,int b){return a%b==0?b:gcd(b,a%b);}

int d[N][30],w[N];//w权值,d的状态定义和上文一样。
int n,k;
vector<int>v[N];

void dfs1(int u,int pr)//一样的套路 ,先算出根节点的情况,
{
	for(int j:v[u])
	{
		if(j==pr)continue;
		dfs1(j,u);
		for(int v1=k;v1>=1;v1--)
		{
			d[u][v1]+=d[j][v1-1];//这个不用考虑容斥,因为已经固定了方向。
		}
	}
}

void dfs2(int u,int pr)
{
	for(int j:v[u])
	{
		if(j==pr)continue;
		
		for(int v1=k;v1>=2;v1--)
         //一定要逆序,然后为了是为了消除d[u][k-2]和d[v][k-1]的情况
		{
			d[j][v1]-=d[j][v1-2];
		}
        //我v1的数量就加上我父节点距离为v1-1的数量。
		for(int v1=1;v1<=k;v1++)
		{
			d[j][v1]+=d[u][v1-1];
		}
		dfs2(j,u);
	}
}

void solve()
{
	cin>>n>>k;
	for(int i=1;i<n;i++)
	{
		int a,b;cin>>a>>b;
		v[a].ps(b);
		v[b].ps(a);
	}	
	for(int i=1;i<=n;i++)cin>>w[i];
	for(int i=1;i<=n;i++)d[i][0]=w[i];//预处理一下等于0的情况
	dfs1(1,-1);
	dfs2(1,-1);
	for(int j=1;j<=n;j++)
	{
			for(int i=1;i<=k;i++)d[j][i]+=d[j][i-1];
	}//这里是处理前缀合,他求的是小于等于k的和
	for(int i=1;i<=n;i++)cout<<d[i][k]<<endl;
}

signed main()
{
	kd;
	int _;_=1;
	//cin>>_;
	while(_--)solve();	
	return 0;
}

拓展二

换根+树的重心。感觉蛮难理解的。

给你一颗树,问你有哪些点可以通过最多一次操作把他变成重心。操作是删除一条边然后再随意连到其他的点上。os(重心:他的子树最大值小于等于n/2)。n<=4e5

思路:

如果一个点可以是重心,那么他的子树中的最大值和除了他的子树外的儿子的最大值需要小于等于n<=2,那么我用一个d1来记录一个点往下的情况,一个d2记录当前点往上的情况。
更新的话有如下几种情况:

d1:

  1. 如果他的子树和小于等于n/2,那么全取,

  2. else 取子树中最大的,并且记录是转移到哪个点,记为p1:d2要用

d2:

  1. 同理:如果n- 他的子树大小<=n/2,那么全取
  2. 往上有两种情况,要么接着往上,要么是从u往下但是不能走d1,因为如果走了p1,就不是除了v的子树外的最值。

d1用一个dfs,d2用一个dfs,然后维护好之后就可以分类讨论这两种情况哪种情况的sz比较大,就处理哪种情况。

const int N=4e5+100,mod=1e9+7;
int lowbit(int x){return x&-x;}
int gcd(int a,int b){return a%b==0?b:gcd(b,a%b);}

int sz[N],d[N],p1[N],p2[N],d2[N];//p1 p2 记录的是最长路和次长路分别是走的哪条边
vector<int>v[N];
int n,ans[N];//ans存一个点是不是可以变成重心。

int dfs(int u,int pr)
{
	sz[u]=1;d[u]=d2[u]=0;
	for(int j:v[u])
	{
		if(j==pr)continue;
		// cout<<u<<" "<<j<<endl;
		dfs(j,u);
		sz[u]+=sz[j];
		if(sz[p1[u]]<sz[j])
		{
			p2[u]=p1[u];
			p1[u]=j;
		}
		else if(sz[p2[u]]<sz[j])p2[u]=j;
		d[u]=max(d[u],d[j]);//d表示u为根的子树最大sz
        
        //这里没啥好说的,
		
	}
	
	if(sz[u]<=n/2)d[u]=sz[u];// 如果可以全取那就全取
	
}

void dfs2(int u,int pr)
{
	for(int j:v[u])
	{
		if(j==pr)continue;
		if(n-sz[j]<=n/2)d2[j]=n-sz[j];// 如果一个点的剩余的和<=n/2那就全部拿
		else if(j==p1[u])d2[j]=max(d2[u],d[p2[u]]);
        //如果他的父亲的最长路和当前点冲突,那么就只能用次长路来更新
		else d2[j]=max(d[p1[u]],d2[u]);
        //同理
		dfs2(j,u);
	}	
    // 这里是比较当前u点的 剩余的全部和最大子树的大小
	if(n-sz[u]>sz[p1[u]])ans[u]=(n-sz[u]-d2[u]<=n/2);
    //如果上头大,就看上头减去上头的最大值能不能满足条件
	else ans[u]=(sz[p1[u]]-d[p1[u]]<=n/2);
    // 如果下头大,就看下头减去最大值能不能满足条件。
}

void solve()
{
	cin>>n;
	for(int i=1;i<n;i++)
	{
		int a,b;cin>>a>>b;
		v[a].ps(b);
		v[b].ps(a);	
	}
	dfs(1,-1);
	dfs2(1,-1);
	
	for(int i=1;i<=n;i++)cout<<ans[i]<<" ";
	cout<<endl;
}

signed main()
{
	kd;
	int _;_=1;
	//cin>>_;
	while(_--)solve();	
	return 0;
}
posted @   黄小轩  阅读(120)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示