题目大意:
给你一颗
N
N
N个节点的树,树上每条边有边权就是遍历的时间,你可以从任意节点出发遍历
N
−
k
N-k
N−k个点并且回到出发点,问你最短的时间是多少?
k
∈
[
0
,
m
i
n
(
N
,
20
)
]
,
N
∈
[
1
,
1
e
4
]
k\in[0,min(N,20)],N\in[1,1e4]
k∈[0,min(N,20)],N∈[1,1e4]
解题思路:
思维误区:一开始我对于这个任意节点出发以为是要换根,但是子父之间的关系不好找!!,而且我定义的状态方程是 d p [ i ] [ j ] dp[i][j] dp[i][j]从 i i i这个子树里不选 j j j个点的花费的最小时间。这个无法更新:因为它一定要联通,我这个方程只表明了不选 j j j个点无法保证联通性!!!
其实这个任意点出发是没关系的:假设我们固定一个
1
1
1号点为根,那么答案肯定是一个连通块,那么连通块里面你从任意一个点出发都可以得到同一个答案,那么我们可以就假设从这个联通块里面深度最小的那个节点出发得到的结果!!
那么为了得到连通块:我们 d p [ i ] [ j ] dp[i][j] dp[i][j]要加上一个限制:就是 i i i这个子树里面不选 j j j个点,并且 i i i这个点一定要选!! 再加上前面的限制就是并且 i i i号点一定是连通块里面深度最低的点!!
那么我们 d p dp dp就很好转移了:如果有多个子树我们分别枚举在两个子树里面分别不选多少就好了!!
更新答案的时候我们要用全局
a
n
s
ans
ans去更新:
假设我们现在到了
u
u
u号点,那么我们默认是
u
u
u子树之外的点是不选的
if(n-siz[u]<=k) ans = min(ans,dp[u][k-(n-siz[u])]);
AC code
#include <bits/stdc++.h>
#define mid ((l + r) >> 1)
#define Lson rt << 1, l , mid
#define Rson rt << 1|1, mid + 1, r
#define ms(a,al) memset(a,al,sizeof(a))
#define log2(a) log(a)/log(2)
#define lowbit(x) ((-x) & x)
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define INF 0x3f3f3f3f
#define LLF 0x3f3f3f3f3f3f3f3f
#define f first
#define s second
#define endl '\n'
using namespace std;
const int N = 2e6 + 10, mod = 1e9 + 9;
const int maxn = 500010;
const long double eps = 1e-5;
const int EPS = 500 * 500;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> PII;
typedef pair<ll,ll> PLL;
typedef pair<double,double> PDD;
template<typename T> void read(T &x) {
x = 0;char ch = getchar();ll f = 1;
while(!isdigit(ch)){if(ch == '-')f*=-1;ch=getchar();}
while(isdigit(ch)){x = x*10+ch-48;ch=getchar();}x*=f;
}
template<typename T, typename... Args> void read(T &first, Args& ... args) {
read(first);
read(args...);
}
vector<PII> G[maxn];
int n,k;
ll dp[maxn][25], ans; // dp[i][j] 表示从 i 这子树里面一定选i这个点并且不选j个点的最小花费
int siz[maxn];
inline void dfs(int u, int fa) {
bool tag = 0;
siz[u] = 1;
for(auto it : G[u]) {
if(it.first == fa) continue;
dfs(it.first,u);
siz[u] += siz[it.first];
if(!tag) { // 判断有多少个子树
for(int i = 0; i <= min(k,siz[it.first]); ++ i) dp[u][i] = dp[it.first][i] + it.second;
if(siz[it.first] <= k) dp[u][siz[it.first]] = 0;
tag = 1;
} else {
ll res[25];
for(int i = 0; i <= k; ++ i) res[i] = LLF;
for(int j = 0; j <= k; ++ j) // 枚举分别不拿多少个?
for(int i = 0; i <= j; ++ i) {
if(siz[it.first] <= j - i)
res[j] = min(res[j],dp[u][i]);
else res[j] = min(res[j],dp[u][i]+dp[it.first][j-i]+it.second);
}
for(int i = 0; i <= k; ++ i) dp[u][i] = res[i];
}
}
if(n-siz[u]<=k) ans = min(ans,dp[u][k-(n-siz[u])]);
if(G[u].size() == 1 && u != 1) dp[u][0] = 0; // 叶子节点更新答案 只有这个是合法的
}
int main() {
IOS;
int _;
cin >> _;
while(_--) {
ans = LLF;
cin >> n >> k;
for(int i = 0; i <= n+1; ++ i) G[i].clear();
for(int i = 1; i < n; ++ i) {
int u, v, w;
cin >> u >> v >> w;
u ++, v ++; // 把编号调到[1,n]
G[u].push_back({v,w});
G[v].push_back({u,w});
}
for(int i = 0; i <= n; ++ i)
for(int j = 0; j <= 20; ++ j)
dp[i][j] = LLF;
dfs(1,0);
cout << ans * 2ll << "\n";
}
}
/*
3
2 0
0 1 3000
4 1
0 1 81
1 2 41
2 3 59
9 2
0 1 1000
1 2 1200
0 3 1000
3 4 1200
0 5 1000
5 6 1200
0 7 1800
7 8 600
*/