[SPOJ - FTOUR2] Free tour II(点分治 + 背包dp + 启发式合并)

6 篇文章 0 订阅
3 篇文章 0 订阅

SPOJ - FTOUR2 Free tour II

problem

给定一棵树,以及 m m m 个拥挤城市编号,选择一条最多包含 k k k 个拥挤城市的简单路径。

每条边有一个有趣度 w w w,可正可负。简单路径的价值定义为包含边的有趣度之和。

求最大价值。 n ≤ 2 e 5 , ∣ w ∣ ≤ 1 e 4 n\le 2e5,|w|\le 1e4 n2e5,w1e4

solution

取点作根,计算过根的符合条件的简单路径价值,如果不过根就转化成各个独立子树的子问题。

选取重心作根,点分治。

问题仍然在于怎么计算过根的符合条件的简单路径价值。

暴力的树形 d p dp dp d p i , j : i dp_{i,j}:i dpi,j:i 子树内拥挤城市为 j j j 的最大路径价值。

每次枚举 i i i 的儿子,每次做树上背包,之后再合并,时间复杂度是平方级别的。

d p u , x + d p v , y ( x + y ≤ k ) → a n s dp_{u,x}+dp_{v,y}(x+y\le k)\rightarrow ans dpu,x+dpv,y(x+yk)ans

d p u , x = max ⁡ ( d p u , x , d p v , x ) dp_{u,x}=\max(dp_{u,x},dp_{v,x}) dpu,x=max(dpu,x,dpv,x)

考虑优化。

对于第 i i i 个儿子而言,如果第 i i i 个儿子使用的拥挤城市数量为 j j j,那么前 i − 1 i-1 i1 个儿子的城市拥挤数量可以使用 1 ∼ k − j 1\sim k-j 1kj

所以可以“前缀和”优化,这里去前缀最大值

d p u , x : dp_{u,x}: dpu,x: i − 1 i-1 i1 个儿子城市拥挤数量使用不超过 x x x 的最大价值。

这样就可以线性枚举 j j j 计算贡献。

最后就是在枚举前 i − 1 i-1 i1 个儿子的拥挤数量问题上。

比如第 p p p 个儿子内部最多可以找到一条路径有 k k k 个拥挤城市,而现在的儿子最多只能有 y ( y < k ) y(y<k) y(y<k) 个。

但是从第 p p p 个儿子开始就必须枚举完使用 k k k 个拥挤城市,才能将前面儿子的信息覆盖完全。

但是这样的时间复杂度就会飞起。

如果交换现在的儿子和第 p p p 个儿子顺序,那么只用枚举完 y y y 个拥挤城市就已经覆盖了前面儿子的所有路径使用情况。

所以将儿子按照内部最多能找到一条路径有 d d d 个拥挤城市, d d d 升序排序。

相当于只将每个点枚举了一次,这就是启发式合并。

code

#include <bits/stdc++.h>
using namespace std;
#define maxn 200005
#define inf 0x7f7f7f7f
vector < pair < int, int > > G[maxn];
int n, m, k, Max, root, N, ans;
bool crowd[maxn], vis[maxn];
int siz[maxn], f[maxn], g[maxn];
struct node { int d, v, w; }MS[maxn];

void dfs( int u, int fa ) {
    int maxsiz = 0; siz[u] = 1;
    for( int i = 0;i < G[u].size();i ++ ) {
        int v = G[u][i].first;
        if( vis[v] or v == fa ) continue;
        else dfs( v, u ), siz[u] += siz[v];
        maxsiz = max( maxsiz, siz[v] );
    }
    maxsiz = max( maxsiz, N - siz[u] );
    if( maxsiz < Max ) Max = maxsiz, root = u;
}

int dfs( int u, int fa, int cnt ) {
    if( cnt == k ) return cnt;
    int ret = cnt;
    for( int i = 0;i < G[u].size();i ++ ) {
        int v = G[u][i].first;
        if( vis[v] or v == fa ) continue;
        ret = max( ret, dfs( v, u, cnt + crowd[v] ) );
    }
    return ret;
}

void dfs( int u, int fa, int val, int cnt ) {
    if( cnt > k ) return;
    g[cnt] = max( g[cnt], val );
    for( int i = 0;i < G[u].size();i ++ ) {
        int v = G[u][i].first, w = G[u][i].second;
        if( vis[v] or v == fa ) continue;
        dfs( v, u, val + w, cnt + crowd[v] );
    }
}

void calc( int u ) {
    if( crowd[u] ) k --;
    int cnt = 0;
    for( int i = 0;i < G[u].size();i ++ ) {
        int v = G[u][i].first, w = G[u][i].second;
        if( vis[v] ) continue;
        MS[++ cnt] = { dfs( v, u, crowd[v] ), v, w };
    }//计算出v子树内一条路最多能有多少个拥挤城市
    sort( MS + 1, MS + cnt + 1, []( node x, node y ) { return x.d < y.d; } );
    //启发式合并
    for( int i = 1;i <= cnt;i ++ ) {
        int v = MS[i].v, w = MS[i].w;
        dfs( v, u, w, crowd[v] ); //计算出v子树内访问x个拥挤城市的最大有趣度
        /*
        f[j]:前i-1个子树信息总和 访问j个拥挤城市的最大值
        g[j]:只针对第i个子树 访问j个拥挤城市的最大值 每次都会在dfn(v,u,w,crowd[v])重新计算
        显然 g[j]+f[x](0<=x<=k-j) 都可以对最终答案进行贡献
        这里对f[x]进行前缀max 有jx平方的时间变成j线性
        实际上x<=MS[i-1].d 要注意这个限制 不然可能会莫须有地更新到不存在的拥挤城市个数 影响f
        然后将i子树信息合并到i-1子树信息内 即g->f
        转到下一个子树i+1
        */
        if( i ^ 1 ) {
            for( int j = 1;j <= MS[i - 1].d;j ++ ) f[j] = max( f[j], f[j - 1] );
            for( int j = 0;j <= MS[i].d;j ++ ) ans = max( ans, f[min( MS[i - 1].d, k - j )] + g[j] );
        }
        for( int j = 0;j <= MS[i].d;j ++ ) f[j] = max( f[j], g[j] ), g[j] = 0;
    }
    for( int i = 0;i <= MS[cnt].d;i ++ ) {
        ans = max( ans, f[i] );
        f[i] = g[i] = 0;
    }
    if( crowd[u] ) k ++;
}

void dfs( int u ) {
    vis[u] = 1;
    calc( u );
    for( int i = 0;i < G[u].size();i ++ ) {
        int v = G[u][i].first;
        if( vis[v] ) continue;
        Max = inf, N = siz[v];
        dfs( v, u );
        dfs( root );
    }
}

int main() {
    scanf( "%d %d %d", &n, &k, &m );
    for( int i = 1, x;i <= m;i ++ ) scanf( "%d", &x ), crowd[x] = 1;
    for( int i = 1, u, v, w;i < n;i ++ ) {
        scanf( "%d %d %d", &u, &v, &w );
        G[u].push_back( { v, w } );
        G[v].push_back( { u, w } );
    }
    Max = inf, N = n;
    dfs( 1, 0 );
    dfs( root );
    printf( "%d\n", ans );
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值