树形dp ---- gym101655 D - Delta Quadrant 树上连通块思维换根 + 树形dp

题目链接


题目大意:

给你一颗 N N N个节点的树,树上每条边有边权就是遍历的时间,你可以从任意节点出发遍历 N − k N-k Nk个点并且回到出发点,问你最短的时间是多少?
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
*/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值