换根dp

例题1
题目大意:给定n个节点的6,找到一个节点,使得所有点的深度之和最大
解题思路:先以节点一为根节点,求出所有点的子树(包含自身)的节点数是多少,用size数组记录。然后是状态转移,从根节点u换到邻接的点v,f[v]=f[u]+(size[1]-size[v])-size[v],以v为根的子树的深度减一,其它的点深度加一

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef pair<ll,ll> PLL;
const int N=1e6+100;
#define int long long

ll n;
vector<ll> Edge[N];
ll size[N];
ll depth[N];// 算出以一为根节点 每个点的深度是多少
ll all_depth;
ll f[N];
ll ans;
ll idx;

ll dfs1(ll u,ll fa)
{
    depth[u]=depth[fa]+1;
    all_depth+=depth[u];
    ll res=1;
    for(auto T_T:Edge[u])
    {
        if(T_T==fa)
            continue;
        res+= dfs1(T_T,u);
    }
    return size[u]=res;
}

void dfs2(ll u,ll fa)
{
    for(auto T_T:Edge[u])
    {
        if(fa==T_T)
            continue;
        f[T_T]=f[u]+(size[1]-size[T_T])-size[T_T];
        ans=max(ans,f[T_T]);
        if(ans==f[T_T])
            idx=T_T;
        dfs2(T_T,u);
    }
}


signed main (void)
{
    cin>>n;
    for(int i=1;i<n;i++)
    {
        ll x,y; cin>>x>>y;
        Edge[x].push_back(y);
        Edge[y].push_back(x);
    }
    depth[0]=-1;
    dfs1(1,0);
    f[1]=all_depth;
    ans=all_depth;
    idx=1;
    dfs2(1,0);
    cout<<idx<<endl;
    return 0;
}

示例二
题目大意:给定一个n个节点的树,每条边有一个权值,每个节点有一定数量的奶牛,找到一个点,使得所有奶牛到这个点所经过的路程最短,输出这个最短距离
解题思路:先以一为根节点进行计算,找到其他节点的子树(包含自身)所包含的奶牛数量是多少,然后进行状态转移,u为根节点,v为相邻的点,dis为u到v的距离,f[v]=f[u]+(size[1]-size[v])*dis-size[v]*dis,以v为子树的点的距离都减去dis,否则都加上dis

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef pair<ll,ll> PLL;
const int N=1e6+100;
#define int long long

ll n;
ll a[N];
vector<PLL> Edge[N];
ll size[N];
ll dis[N];// 算出以一为根节点
ll all_dis;
ll f[N];
ll ans;
ll idx;

ll dfs1(ll u,ll fa)
{
    ll res=a[u];
    all_dis+=a[u]*dis[u];
    for(auto T_T:Edge[u])
    {
        if(T_T.first==fa)
            continue;
        dis[T_T.first]=dis[u]+T_T.second;
        res+= dfs1(T_T.first,u);
    }
    return size[u]=res;
}

void dfs2(ll u,ll fa)
{
    for(auto T_T:Edge[u])
    {
        if(fa==T_T.first)
            continue;
        f[T_T.first]=f[u]+(size[1]-size[T_T.first])*T_T.second-size[T_T.first]*T_T.second;
        ans=min(ans,f[T_T.first]);
        if(ans==f[T_T.first])
            idx=T_T.first;
        dfs2(T_T.first,u);
    }
}


signed main (void)
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    for(int i=1;i<n;i++)
    {
        ll x,y,z; cin>>x>>y>>z;
        Edge[x].push_back({y,z});
        Edge[y].push_back({x,z});
    }
    dfs1(1,0);
    f[1]=all_dis;
    ans=all_dis;
    idx=1;
    dfs2(1,0);
    cout<<ans<<endl;
    return 0;
}

示例三
题目大意:给你一个n个结点的树,每条边都有一个权值,找到一个点,找到以它为根节点,其他节点到它的最远距离最短,输出这个最短距离
解题思路:先以1这个节点为根节点,找到以这个节点为根节点,其他节点到这个节点的最远距离和次元距离f[1][1]次远,f[1][0]最远。然后进行状态转移。u为根节点,v为相邻的点,如果v是u最长路径上的点,那么v的最远距离就可以是u的次元距离加上uv之间的距离,否则就拿最长路径上的点来更新。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N=1e6+100;
#define int long long 
typedef pair<ll,ll> PLL;


// 怎么找到根节点和其他点之间距离的最大值呢  




// 假设找到了节点u的 但是怎么转移呢

// 首先暴力搜索找到第一个点的

// 除了存这个点的最远距离 还得存这个点的次远距离
// eiei 起码差不多是吧  只不过我想全部存完
/*
	对于一个节点从父亲节点推过来
	如果该节点是父亲节点连上最长的一环 那么这个节点只能从父亲节点的次大值更新
	最开始求的时候就必须保证最大值和次大值 不在一条链上 
	如果不是最长链上的一个点 那么直接从父亲节点上的最大值更新
*/


ll n;
vector<PLL> edge[N];
priority_queue<ll,vector<ll>,greater<ll>> save[N];
ll f[N];
ll ans=1e18;
// 先找到以1为根节点 所有点的最长 和次长距离
// 对的  只要找到第一个点的就好了

ll dfs_find(ll u,ll fa)
{
	ll maxn=0;
	for(auto [x,y]:edge[u])
	{
		if(x==fa)
			continue;
		ll len1=dfs_find(x,u)+y;
		maxn=max(maxn,len1);
		save[u].push(len1);
		if(save[u].size()>2)
			save[u].pop();
	}	
	return f[u]=maxn;
}


void dfs_maxn(ll u,ll fa)
{
	while(save[u].size()<2)
		save[u].push(0);
	ll first=save[u].top();
	save[u].pop();
	ll second=save[u].top();
	save[u].pop();
	ans=min(ans,second);
	for(auto [x,y]:edge[u])
	{
		if(fa==x)
			continue;
		// 从根节点转移过来
		if(f[x]+y==second)
			save[x].push(first+y);
		else
			save[x].push(second+y);
		while(save[x].size()>2)
			save[x].pop();
		dfs_maxn(x,u);
	}
	save[u].push(first),save[u].push(second);
}


signed main (void)
{
	cin>>n;
	for(int i=1;i<n;i++)
	{
		ll x,y,z;	cin>>x>>y>>z;
		edge[x].push_back({y,z});
		edge[y].push_back({x,z});
	}
	dfs_find(1,0);
	while(save[1].size()<2)
		save[1].push(0);
	// 然后开始进行第二遍搜索
	dfs_maxn(1,0);
//		for(int i=1;i<=n;i++)
//		{
//			cout<<i<<" ";
//			while(save[i].size())
//				cout<<save[i].top()<<" ",save[i].pop();
//			cout<<endl;
//			cout<<f[i]<<"--------------"<<endl;
//		}
	cout<<ans<<endl;
	return 0;
}

参考代码出处

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef pair<ll,ll> PLL;
const int N=1e6+100;
#define int long long

ll n;
vector<PLL> Edge[N];
ll f[N][2];
ll ans=1e18;

void dfs1(ll u,ll fa)
{
    for(auto T_T:Edge[u])
    {
        if(T_T.first==fa)
            continue;
        dfs1(T_T.first,u);
        // 用孩子节点的距离更新当前点的最远和次元距离
        if(f[T_T.first][0]+T_T.second>f[u][0])
            f[u][1]=f[u][0],f[u][0]=f[T_T.first][0]+T_T.second;
        else if(f[T_T.first][0]+T_T.second>f[u][1])
            f[u][1]=f[T_T.first][0]+T_T.second;
    }
}

// len 以u为根节点时除了自身子树之外的最大深度
void dfs2(ll u,ll fa,ll len)
{
    ans=min(ans,max(len,f[u][0]));
    for(auto T_T:Edge[u])
    {
        if(fa==T_T.first)
            continue;
        // 换根
        if(f[T_T.first][0]+T_T.second==f[u][0]) {
            dfs2(T_T.first,u,max(f[u][1],len)+T_T.second);
        }
        else
            dfs2(T_T.first,u,max(f[u][0],len)+T_T.second);
    }
}


signed main (void)
{
    cin>>n;
    for(int i=1;i<n;i++)
    {
        ll x,y,z; cin>>x>>y>>z;
        Edge[x].push_back({y,z});
        Edge[y].push_back({x,z});
    }
    dfs1(1,0);
    dfs2(1,0,0);
    cout<<ans<<endl;
    return 0;
}

与示例三相似的题目

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef pair<ll,ll> PLL;
const int N=1e6+100;
#define int long long

ll n;
vector<PLL> Edge[N];
ll f[N][2];
ll ans[N];

void dfs1(ll u,ll fa)
{
    for(auto T_T:Edge[u])
    {
        if(T_T.first==fa)
            continue;
        dfs1(T_T.first,u);
        // 用孩子节点的距离更新当前点的最远和次元距离
        if(f[T_T.first][0]+T_T.second>f[u][0])
            f[u][1]=f[u][0],f[u][0]=f[T_T.first][0]+T_T.second;
        else if(f[T_T.first][0]+T_T.second>f[u][1])
            f[u][1]=f[T_T.first][0]+T_T.second;
    }
}

// len 以u为根节点时除了自身子树之外的最大深度
void dfs2(ll u,ll fa,ll len)
{
    ans[u]=max(len,f[u][0]);
    for(auto T_T:Edge[u])
    {
        if(fa==T_T.first)
            continue;
        // 换根
        if(f[T_T.first][0]+T_T.second==f[u][0]) {
            dfs2(T_T.first,u,max(f[u][1],len)+T_T.second);
        }
        else
            dfs2(T_T.first,u,max(f[u][0],len)+T_T.second);
    }
}


signed main (void)
{
    cin>>n;
    for(int i=2;i<=n;i++)
    {
        ll x,y; cin>>x>>y;
        Edge[x].push_back({i,y});
        Edge[i].push_back({x,y});
    }
    dfs1(1,0);
    dfs2(1,0,0);
    for(int i=1;i<=n;i++)
        cout<<ans[i]<<endl;
    return 0;
}

示例四
题目大意:给你一个n个结点的树,最初是以一号节点为根,所获得的收益是其他节点到根节点的最远距离,每次换根需要耗费c元,询问你最大利益是多少
解题思路:与示例三相同,不过需要在第二次dfs的时候记录一下当前是第几次换根

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef pair<ll,ll> PLL;
const int N=1e6+100;
#define int long long

ll t;
ll n,k,c;
ll f[N][2];
ll ans;

signed main (void)
{
    cin>>t;
    while(t--) {
        ans = -1e18;
        cin >> n >> k >> c;
        vector<vector<PLL>> Edge(n + 10);
        vector<vector<ll>> f(n+10, vector<ll>(2, 0));

        for (int i = 2; i <= n; i++) {
            ll x, y;
            cin >> x >> y;
            Edge[x].push_back({y, k});
            Edge[y].push_back({x, k});
        }

        function<void(int, int)> dfs1 = [&](ll u, ll fa) {
            for (auto T_T: Edge[u]) {
                if (T_T.first == fa)
                    continue;
                dfs1(T_T.first, u);
                // 用孩子节点的距离更新当前点的最远和次元距离
                if (f[T_T.first][0] + T_T.second > f[u][0])
                    f[u][1] = f[u][0], f[u][0] = f[T_T.first][0] + T_T.second;
                else if (f[T_T.first][0] + T_T.second > f[u][1])
                    f[u][1] = f[T_T.first][0] + T_T.second;
            }
        };

        function<void(int, int, int, int)> dfs2 = [&](ll u, ll fa, ll len, ll cnt) {
            ans = max(ans, max(f[u][0], len) - cnt * c);
            for (auto T_T: Edge[u]) {
                if (fa == T_T.first)
                    continue;
                // 换根
                if (f[T_T.first][0] + T_T.second == f[u][0]) {
                    dfs2(T_T.first, u, max(f[u][1], len) + T_T.second, cnt + 1);
                } else
                    dfs2(T_T.first, u, max(f[u][0], len) + T_T.second, cnt + 1);
            }
        };
        dfs1(1, 0);
        dfs2(1, 0, 0, 0);
        cout << ans << endl;
    }
    return 0;
}

示例五
题目大意:给你一个n个节点的树,每条边都有一个权值,询问有多少个i,j(i<j)的距离是2019的倍数

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef pair<ll,ll> PLL;
const int N=1e6+100;
#define int long long

ll t;
ll n;
ll ans;

signed main (void)
{
    while(cin>>n) {
        ans = 0;
        vector<vector<PLL>> Edge(n + 10);
        vector<vector<ll>> dis(n+10, vector<ll>(3000, 0));
        //以第i个点为根节点  第i个点子树 它的路径长度为j的方案数是多少
        vector<ll> num(3000,0);

        for (int i = 1; i < n; i++) {
            ll x, y,z;
            cin >> x >> y>>z;
            Edge[x].push_back({y, z});
            Edge[y].push_back({x, z});
        }



        function<void(int, int)> dfs1 = [&](ll u, ll fa) {
            for (auto T_T: Edge[u]) {
                if (T_T.first == fa)
                    continue;
                dfs1(T_T.first, u);
                dis[u][T_T.second]+=1;
                // 用孩子节点的距离更新当前点的最远和次元距离
                for(int i=0;i<2019;i++)
                    dis[u][(i+T_T.second)%2019]+=dis[T_T.first][i];
            }
        };

        function<void(int, int)> dfs2 = [&](ll u, ll fa) {
            ans+=dis[u][0];
            for (auto T_T: Edge[u]) {
                if (fa == T_T.first)
                    continue;
                // 换根
                // 先算算那一部分的路径是应该减去T_T.second 那一部分的路径是加上T_T.second
                for(int i=0;i<2019;i++)
                    num[(i+T_T.second)%2019]=dis[u][(i+T_T.second)%2019]-dis[T_T.first][i];
                // num这一部分就是加上2019的部分
                num[T_T.second]--;
                dis[T_T.first][T_T.second]++;
                for(int i=0;i<2019;i++)
                    dis[T_T.first][(i+T_T.second)%2019]+=num[i];
                dfs2(T_T.first,u);
            }
        };
        dfs1(1, 0);
        dfs2(1, 0);
        cout << ans/2 << endl;
    }
    return 0;
}

示例六
题目大意:有一颗n个节点的树,每一个节点都有一个ci的权值,假定以i为根节点,那么需要花费每个节点的c乘上每个节点到i节点的节点数的和,询问最小的花费是多少

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef pair<ll,ll> PLL;
const int N=1e6+100;
#define int long long

ll n;
vector<ll> Edge[N];
ll f[N];
ll all_depth;
ll ans;
ll val[N];
ll  a[N];

// 咱就是说这一步求的应该是什么呢

void dfs1(ll u,ll fa,ll depth)
{
    f[1]+=depth*a[u];
    val[u]=a[u];
    for(auto T_T:Edge[u])
    {
        if(T_T==fa)
            continue;
        dfs1(T_T,u,depth+1);
        val[u]+=val[T_T];
    }
}

void dfs2(ll u,ll fa)
{
    for(auto T_T:Edge[u])
    {
        if(fa==T_T)
            continue;
        f[T_T]=f[u]-val[T_T]*2+val[1];
        ans=min(ans,f[T_T]);
        dfs2(T_T,u);
    }
}


signed main (void)
{
    cin>>n;
    for(int i=1;i<n;i++)
    {
        ll x,y; cin>>x>>y;
        Edge[x].push_back(y);
        Edge[y].push_back(x);
    }
    for(int i=1;i<=n;i++)
        cin>>a[i];
    dfs1(1,0,0);
    ans=f[1];
    dfs2(1,0);
    cout<<ans<<endl;
    return 0;
}
  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值