Rinne 最近了解了如何快速维护可支持插入边删除边的图,并且高效的回答一下奇妙的询问。
她现在拿到了一个 n 个节点 m 条边的无向连通图,每条边有一个边权
现在她想玩一个游戏:选取一个 “重要点” S,然后选择性删除一些边,使得原图中所有除 S 之外度为 1 的点都不能到达 S。
定义删除一条边的代价为这条边的边权,现在 Rinne 想知道完成这个游戏的最小的代价,这样她就能轻松到达 rk1 了!作为回报,她会让你的排名上升一定的数量。
第一行三个整数 N,M,S,意义如「题目描述」所述。
接下来 M 行,每行三个整数 u,v,w 代表点 u 到点 v 之间有一条长度为 w 的无向边。
一个整数表示答案。
4 3 1 1 2 1 1 3 1 1 4 1
3
需要使得点 2,3,4 不能到达点 1,显然只能删除所有的边,答案为 3
4 3 1 1 2 3 2 3 1 3 4 2
1
需要使得点 4 不能到达点 1,显然删除边是最优的。
,保证答案在 C++ long long 范围内。
最开始读题,很容易想到用dfs,但是又得保证全局最优解,这时候只用dfs就不行了,需要用dfs加dp,所以这是一道树形dp
我们只需要推出状态转移方程
除了s以外度为1的点,不难想到就是叶节点,我们遍历到叶节点时将叶节点的dp值初始化为INF,因为我们最开始肯定想把连着叶节点的那条边删去,后面再做优化,看有没有总和更小的方法,假设一个情况是,一条主干分了两个叶节点,我们肯定先算出了两个叶节点边切断的边权和,如果这条主干有边权更小的,直接取这个最小值即可
因此状态转移方程为dp[u] += min(dp[v] , w)
接下来只需要写出代码即可:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define debug(x) cerr << #x << ": " << x << '\n';
const int INF = 0x3f3f3f3f3f3f3f3f;
//倒序不要忘记是--不是++
void solve(){
int n,m,s;cin>>n>>m>>s;
vector<vector<pair<int,int>>> e(n+1);
for(int i=1;i<=m;i++){
int u,v,w;cin>>u>>v>>w;
e[u].push_back({v,w});
e[v].push_back({u,w});
}
vector<int> dp(n+1);
auto dfs=[&](auto dfs,int u,int fa)->void{
if(u!=s&&e[u].size()==1) dp[u]=INF;
// debug(u);
for(auto i:e[u]){
auto [v,w]=i;
if(v==fa) continue;
dfs(dfs,v,u);
dp[u]+=min(dp[v],w);
}
};
dfs(dfs,s,0);
cout<<dp[s];
}
signed main(){
std::ios_base::sync_with_stdio(false);
std::cin.tie(nullptr);
int t = 1;
// cin>>t;
while(t--){
solve();
}return 0;
}