换根dp学习笔记
作者:互联网
换根DP
换 根 d p 一 般 的 三 步 骤 换根dp一般的三步骤 换根dp一般的三步骤:
- 任意选择一个节点作为根
- 统计子树(自底向上)对每个根节点的贡献
- (自顶向下)统计父节点对子节点的贡献并计算答案
Problem A:P3478 [POI2008]STA-Station
// Problem: P3478 [POI2008]STA-Station
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3478
// Memory Limit: 125 MB
// Time Limit: 2000 ms
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <queue>
#include <set>
#include <map>
#include <vector>
#define pb push_back
#define in insert
#define mem(f, x) memset(f,x,sizeof(f))
#define fo(i,a,n) for(int i=(a);i<=(n);++i)
#define fo_(i,a,n) for(int i=(a);i<(n);++i)
#define debug(x) cout<<#x<<":"<<x<<endl;
#define endl '\n'
using namespace std;
template<typename T>
ostream& operator<<(ostream& os,const vector<T>&v){for(int i=0,j=0;i<v.size();i++,j++)if(j>=5){j=0;puts("");}else os<<v[i]<<" ";return os;}
template<typename T>
ostream& operator<<(ostream& os,const set<T>&v){for(auto c:v)os<<c<<" ";return os;}
typedef pair<int,int>PII;
typedef pair<long,long>PLL;
typedef long long ll;
typedef unsigned long long ull;
const int N=1e6+10,M=1e9+7;
ll n,m,_;
int h[N],ne[N*2],e[N*2],idx;
int dis[N],cnt[N];
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u,int fa)
{
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(j==fa)continue;
cnt[u]+=cnt[j];
dfs(j,u);
dis[u]=max(dis[u],dis[j]+cnt[j]+1);
}
if(!cnt[u])cnt[u]=1;//叶子节点
}
void solve()
{
cin>>n;
mem(h,-1);
for(int i=1;i<=n-1;i++)
{
int u,v;scanf("%d%d",&u,&v);
add(u,v),add(v,u);
}
dfs(1,-1);
for(int i=1;i<=n;i++)
{
cout<<i<<" "<<cnt[i]<<" "<<dis[i]<<endl;
}
}
int main()
{
solve();
return 0;
}
今天要疯狂一下!
刚才好像体会到了大师我悟了的感觉
Q_1
给 定 一 棵 树 , 以 1 号 节 点 为 根 节 点 , 统 计 树 种 每 个 节 点 的 深 入 记 入 d e p [ N ] 数 组 , 统 计 以 每 个 节 点 u 为 根 的 子 树 的 节 点 数 目 计 入 c n t [ N ] 数 组 给定一棵树,以1号节点为根节点,统计树种每个节点的深入记入dep[N]数组,\\ 统计以每个节点u为根的子树的节点数目计入cnt[N]数组 给定一棵树,以1号节点为根节点,统计树种每个节点的深入记入dep[N]数组,统计以每个节点u为根的子树的节点数目计入cnt[N]数组
体 会 深 度 是 从 根 节 点 , 自 顶 向 下 递 推 得 到 的 , 子 节 点 树 木 是 从 树 的 底 部 , 自 底 向 上 递 推 得 到 的 , 那 么 树 形 d p 种 的 换 根 也 是 一 样 , 两 种 递 推 方 式 对 应 的 代 码 如 下 体会深度是从根节点,自顶向下递推得到的,子节点树木是从树的底部,自底向上\\ 递推得到的,那么树形dp种的换根也是一样,两种递推方式对应的代码如下 体会深度是从根节点,自顶向下递推得到的,子节点树木是从树的底部,自底向上递推得到的,那么树形dp种的换根也是一样,两种递推方式对应的代码如下
void init()
{
for(int i=1;i<=n;i++)cnt[i]=1;
}
void dfs(int u,int fa)
{
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(j==fa)continue;
dep[j]=dep[u]+1;//由根节点更新子节点
dfs(j,u);
cnt[u]+=cnt[j];//得到了子节点的相关信息,由子节点更新根节点
}
}
Problem B:P2986 [USACO10MAR]Great Cow Gathering G
说 实 话 , 写 了 一 半 的 时 候 , 突 然 收 到 数 字 逻 辑 下 周 周 四 考 的 消 息 , 我 T M 还 没 复 习 呢 , 在 纸 上 写 了 写 , 自 己 的 草 稿 是 真 的 烂 , 而 且 不 会 分 析 换 根 之 后 根 对 子 节 点 的 影 响 只 写 了 第 一 个 d f s , 然 后 写 了 一 个 暴 力 说实话,写了一半的时候,突然收到数字逻辑下周周四考的消息,\\ 我TM还没复习呢,在纸上写了写,自己的草稿是真的烂,\\ 而且不会分析换根之后根对子节点的影响 只写了第一个dfs,然后写了一个暴力 说实话,写了一半的时候,突然收到数字逻辑下周周四考的消息,我TM还没复习呢,在纸上写了写,自己的草稿是真的烂,而且不会分析换根之后根对子节点的影响只写了第一个dfs,然后写了一个暴力
又因为%lld写成了%%lld一直debug
然
后
是
c
n
t
[
u
]
+
=
c
n
t
[
j
]
,
写
成
了
c
n
t
[
u
]
+
=
W
[
j
]
才
写
好
暴
力
,
太
菜
了
吧
!
然后是cnt[u]+=cnt[j],写成了cnt[u]+=W[j]才写好暴力,太菜了吧!
然后是cnt[u]+=cnt[j],写成了cnt[u]+=W[j]才写好暴力,太菜了吧!
// Problem: P2986 [USACO10MAR]Great Cow Gathering G
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P2986
// Memory Limit: 125 MB
// Time Limit: 1000 ms
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <queue>
#include <set>
#include <map>
#include <vector>
#define pb push_back
#define in insert
#define mem(f, x) memset(f,x,sizeof(f))
#define fo(i,a,n) for(int i=(a);i<=(n);++i)
#define fo_(i,a,n) for(int i=(a);i<(n);++i)
#define debug(x) cout<<#x<<":"<<x<<endl;
#define endl '\n'
using namespace std;
template<typename T>
ostream& operator<<(ostream& os,const vector<T>&v){for(int i=0,j=0;i<v.size();i++,j++)if(j>=5){j=0;puts("");}else os<<v[i]<<" ";return os;}
template<typename T>
ostream& operator<<(ostream& os,const set<T>&v){for(auto c:v)os<<c<<" ";return os;}
typedef pair<int,int>PII;
typedef pair<long,long>PLL;
typedef long long ll;
typedef unsigned long long ull;
const int N=1e5+10,M=1e9+7;
ll n,m,_;
int h[N],ne[N*2],e[N*2],idx;
ll w[N*2],W[N];//边权,点权
ll g[N],cnt[N];
void dfs(int u,int fa)
{
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(j==fa)continue;
dfs(j,u);
cnt[u]+=cnt[j];
g[u]+=g[j]+w[i]*cnt[j];
}
}
void add(int a,int b,ll c)
{
e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
void init()
{
for(int i=1;i<=n;i++)
cnt[i]=W[i],g[i]=0;
}
void solve()
{
cin>>n;
mem(h,-1);
for(int i=1;i<=n;i++)scanf("%lld",&W[i]);
for(int i=1;i<n;i++)
{
int a,b;
ll c;
scanf("%d%d%lld",&a,&b,&c);
add(a,b,c);
add(b,a,c);
}
ll ans=0x3f3f3f3f3f3f3f3f;
for(int i=1;i<=n;i++)
{
init();
dfs(i,-1);
ans=min(ans,g[i]);
}
cout<<ans<<endl;
}
int main()
{
solve();
return 0;
}
全程自己验算,各种debug,没看题解推导出来了,太开心了
// Problem: P2986 [USACO10MAR]Great Cow Gathering G
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P2986
// Memory Limit: 125 MB
// Time Limit: 1000 ms
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <queue>
#include <set>
#include <map>
#include <vector>
#define pb push_back
#define in insert
#define mem(f, x) memset(f,x,sizeof(f))
#define fo(i,a,n) for(int i=(a);i<=(n);++i)
#define fo_(i,a,n) for(int i=(a);i<(n);++i)
#define debug(x) cout<<#x<<":"<<x<<endl;
#define endl '\n'
using namespace std;
template<typename T>
ostream& operator<<(ostream& os,const vector<T>&v){for(int i=0,j=0;i<v.size();i++,j++)if(j>=5){j=0;puts("");}else os<<v[i]<<" ";return os;}
template<typename T>
ostream& operator<<(ostream& os,const set<T>&v){for(auto c:v)os<<c<<" ";return os;}
typedef pair<int,int>PII;
typedef pair<long,long>PLL;
typedef long long ll;
typedef unsigned long long ull;
const int N=1e5+10,M=1e9+7;
ll n,m,_;
int h[N],ne[N*2],e[N*2],idx;
ll w[N*2],W[N];//边权,点权
ll g[N],cnt[N];//sum==cnt[1]; 需要g[1],dfs_up不需要g了
ll f[N];
void dfs(int u,int fa)
{
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(j==fa)continue;
dfs(j,u);
cnt[u]+=cnt[j];
g[u]+=g[j]+w[i]*cnt[j];
}
}
void dfs_up(int u,int fa)
{
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(j==fa)continue;
f[j]=f[u]+w[i]*(cnt[1]-2*cnt[j]);
dfs_up(j,u);
}
}
void add(int a,int b,ll c)
{
e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
void solve()
{
cin>>n;
mem(h,-1);
for(int i=1;i<=n;i++)scanf("%lld",&W[i]);
for(int i=1;i<n;i++)
{
int a,b;
ll c;
scanf("%d%d%lld",&a,&b,&c);
add(a,b,c);
add(b,a,c);
}
for(int i=1;i<=n;i++)
cnt[i]=W[i];
dfs(1,-1);
f[1]=g[1];//NB!
dfs_up(1,-1);
ll ans=0x3f3f3f3f3f3f3f3f;
for(int i=1;i<=n;i++)
{
ans=min(ans,f[i]);
}
cout<<ans<<endl;
}
int main()
{
solve();
return 0;
}
Problem C:Tree with Maximum Cost
超级开心,自己写出来了(虽然有点水)某次div3的F题
// Problem: F. Tree with Maximum Cost
// Contest: Codeforces - Codeforces Round #527 (Div. 3)
// URL: https://codeforces.com/problemset/problem/1092/F
// Memory Limit: 256 MB
// Time Limit: 2000 ms
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <queue>
#include <set>
#include <map>
#include <vector>
#define pb push_back
#define in insert
#define mem(f, x) memset(f,x,sizeof(f))
#define fo(i,a,n) for(int i=(a);i<=(n);++i)
#define fo_(i,a,n) for(int i=(a);i<(n);++i)
#define debug(x) cout<<#x<<":"<<x<<endl;
#define endl '\n'
using namespace std;
template<typename T>
ostream& operator<<(ostream& os,const vector<T>&v){for(int i=0,j=0;i<v.size();i++,j++)if(j>=5){j=0;puts("");}else os<<v[i]<<" ";return os;}
template<typename T>
ostream& operator<<(ostream& os,const set<T>&v){for(auto c:v)os<<c<<" ";return os;}
typedef pair<int,int>PII;
typedef pair<long,long>PLL;
typedef long long ll;
typedef unsigned long long ull;
const int N=2e5+10,M=1e9+7;
ll n,m,_;
int h[N],ne[N*2],e[N*2],idx;
ll w[N];
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
ll Sum,sum[N];//原来是sum[N]忘记抛弃自己了
ll g[N],f[N];
void dfs(int u,int fa)
{
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(j==fa)continue;
dfs(j,u);
sum[u]+=sum[j]+w[j];
g[u]+=g[j]+sum[j]+w[j];
}
}
void dfs_up(int u,int fa)
{
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(j==fa)continue;
// f[j]=g[j]+f[u]-(w[j]+g[j]+sum[j])+(Sum-sum[j]-w[j]);
f[j]=f[u]-2*w[j]-2*sum[j]+Sum;//化简之后,发现一个规律,不会用到g[j]的,而且-2*w[j]
dfs_up(j,u);
}
}
void solve()
{
cin>>n;
mem(h,-1);
for(int i=1;i<=n;i++)
scanf("%lld",&w[i]);
fo(i,1,n-1)
{
int u,v;scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
dfs(1,-1);
Sum=sum[1]+w[1];//Sum=
// debug(Sum);
f[1]=g[1];
// for(int i=1;i<=n;i++)//没问题
// {
// cout<<i<<" "<<sum[i]<<" "<<g[i]<<endl;
// }
// cout<<g[1]<<endl;//没问题
dfs_up(1,-1);
ll ans=-1;
for(int i=1;i<=n;i++)
{
ans=max(ans,f[i]);
// cout<<i<<" "<<f[i]<<endl;
}
cout<<ans<<endl;
}
int main()
{
solve();
return 0;
}
Problem DP3047 [USACO12FEB]Nearby Cows G
全 程 手 推 , 自 己 d e b u g , 虽 然 知 道 了 树 形 d p 的 套 路 , 但 是 还 是 做 的 有 点 慢 , 不 过 最 后 所 有 的 数 据 和 自 己 想 到 的 都 完 全 一 样 的 时 候 , 真 的 超 级 开 心 全程手推,自己debug,虽然知道了树形dp的套路,但是还是做的有点慢,不过最后 \\ 所有的数据和自己想到的都完全一样的时候,真的超级开心 全程手推,自己debug,虽然知道了树形dp的套路,但是还是做的有点慢,不过最后所有的数据和自己想到的都完全一样的时候,真的超级开心
又悟道了一点
写的时候我觉得dp的时候就向背包一样需要倒序遍历,经过实验发现不需要,
其实因为是树形dp,那么dfs之后,通过j更新父节点u的时候一定是不同层的,
所以正序和倒序都对。
for(int m=0;m<=k;m++)
ans[j][m]=f[j][m]+ans[u][m-1]-f[j][m-2];
for(int m=k;m;m--)
{
ans[j][m]=f[j][m]+ans[u][m-1]-f[j][m-2];
}
// Problem: P3047 [USACO12FEB]Nearby Cows G
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3047
// Memory Limit: 125 MB
// Time Limit: 1000 ms
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <queue>
#include <set>
#include <map>
#include <vector>
#define pb push_back
#define in insert
#define mem(f, x) memset(f,x,sizeof(f))
#define fo(i,a,n) for(int i=(a);i<=(n);++i)
#define fo_(i,a,n) for(int i=(a);i<(n);++i)
#define debug(x) cout<<#x<<":"<<x<<endl;
#define endl '\n'
using namespace std;
template<typename T>
ostream& operator<<(ostream& os,const vector<T>&v){for(int i=0,j=0;i<v.size();i++,j++)if(j>=5){j=0;puts("");}else os<<v[i]<<" ";return os;}
template<typename T>
ostream& operator<<(ostream& os,const set<T>&v){for(auto c:v)os<<c<<" ";return os;}
typedef pair<int,int>PII;
typedef pair<long,long>PLL;
typedef long long ll;
typedef unsigned long long ull;
const int N=2e5+10,M=1e9+7;
ll n,m,_,k;
int h[N],e[N],ne[N],idx;
ll w[N];
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
ll f[N][25];
ll ans[N][25];//res==max(res,ans[i][k]);
void dfs(int u,int fa)
{
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(j==fa)continue;
dfs(j,u);
for(int m=1;m<=k;m++)
f[u][m]+=f[j][m-1];
//
// for(int m=k;m>=1;m--)
// {
// f[u][m]+=f[j][m-1];
// }
}
}
void Dfs(int u,int fa)
{
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(j==fa)continue;
for(int m=0;m<=k;m++)
ans[j][m]=f[j][m]+ans[u][m-1]-f[j][m-2];
// for(int m=k;m;m--)
// {
// ans[j][m]=f[j][m]+ans[u][m-1]-f[j][m-2];
// }
//
Dfs(j,u);
}
}
void solve()
{
cin>>n>>k;
mem(h,-1);
for(int i=1;i<n;i++)
{
int u,v;scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
for(int i=1;i<=n;i++)scanf("%lld",&w[i]);
for(int i=1;i<=n;i++)//初始化就很nice
{
f[i][0]=w[i];
for(int j=1;j<=k;j++)
f[i][j]+=f[i][0];
}
dfs(1,-1);
// for(int i=1;i<=n;i++)//正确
// {
// for(int j=0;j<=k;j++)
// cout<<f[i][j]<<" ";
// cout<<endl;
// }
for(int i=0;i<=k;i++)
{
ans[1][i]=f[1][i];
}
for(int i=1;i<=n;i++)
ans[i][0]=f[i][0];
Dfs(1,-1);
for(int i=1;i<=n;i++)
{
cout<<ans[i][k]<<endl;
}
}
int main()
{
solve();
return 0;
}
标签:cnt,idx,int,ll,笔记,dfs,include,换根,dp 来源: https://blog.csdn.net/hacker__man/article/details/117534619