一种换根 dp 的做法,但是是一种和题解区不同的做法。
由于这种树上路径的问题若假定一个点为根会更加的好求,所以我们可以枚举 xxx,然后假定 xxx 为根节点,再去找有多少个满足条件的 yyy。
显然有对于每个 xxx 复杂度为 O(n)O(n)O(n) 的搜索,记录 u,valu,valu,val 分别表示目前的点的编号 uuu,以及 xxx 到 uuu 走过的最大权值 valvalval,记录 valvalval 的原因是需要根据目前的路径最大权值选择可以走过的边,例如 valvalval 为 111 的时候就不可以经过权值为 000 的边了。答案显然就是每一个 xxx 可以到达的 uuu 的数量。复杂度为 O(n2)O(n^2)O(n2),需要优化。
发现 (u,val)(u,val)(u,val) 的总个数就只有 2×n2 \times n2×n 个,如果使用记忆化搜索的话可以把时间复杂度优化到 O(n)O(n)O(n)。记录 dpu,valdp_{u,val}dpu,val 表示从根节点 xxx 走到 uuu 时,路径最大权值为 valvalval 时,uuu 可以到达的点的个数。
但是这样显然不可行,在不同的 xxx 下面这个值可以是不同的。
于是我们可以先跑出来根节点为 111 的所有 dp 的值,然后再跑换根 dp,这个时候已经和前面提到的搜索没多大关系了。
前面的树形 dp 的转移方程很好想,就是对于每一个 uuu 枚举可以到的点 vvv 并得到两点之间的边的权值 www,如果 w=0w=0w=0,就是 dpu,0+dpv,0+1→dpu,0dp_{u,0}+dp_{v,0}+1 \to dp_{u,0}dpu,0+dpv,0+1→dpu,0,否则就是 dpu,0+dpv,1+1→dpu,0dp_{u,0}+dp_{v,1}+1 \to dp_{u,0}dpu,0+dpv,1+1→dpu,0 并且 dpu,1+dpv,1+1→dpu,1dp_{u,1}+dp_{v,1}+1 \to dp_{u,1}dpu,1+dpv,1+1→dpu,1。
然后的换根 dp 就是这样的反着来,最后答案显然就是对于每一个根节点 xxx 下面的 dpx,0dp_{x,0}dpx,0 即可。具体可以看代码。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
const int N=200010;
vector<pair<int,int> > v[N];
int dp[N][2];//dp数组
int ans=0;
void dfs(int u,int pre){
for(auto [i,w]:v[u]){
if(i==pre) continue;
dfs(i,u);
if(w==0) dp[u][0]+=dp[i][0]+1;
else dp[u][0]+=dp[i][1]+1,dp[u][1]+=dp[i][1]+1;//转移方程
}
}
void change(int u,int i,int w){
if(w==0) dp[u][0]-=dp[i][0]+1;
else dp[u][0]-=dp[i][1]+1,dp[u][1]-=dp[i][1]+1;//反着来,先断边再加边
if(w==0) dp[i][0]+=dp[u][0]+1;
else dp[i][0]+=dp[u][1]+1,dp[i][1]+=dp[u][1]+1;
}
void dfs2(int u,int pre){
ans+=dp[u][0];
for(auto [i,w]:v[u]){
if(i==pre) continue;
change(u,i,w);//换根
dfs2(i,u);
change(i,u,w);
}
}
signed main(){
cin>>n;
for(int i=1;i<n;i++){
int x,y,w;
cin>>x>>y>>w;
v[x].push_back({y,w});
v[y].push_back({x,w});
}
dfs(1,0);//树形dp
dfs2(1,0);//换根dp
cout<<ans;
return 0;
}
如果对本题题解有疑问,欢迎在讨论区提出,我会尽量给出解答。