「カンプツリーDP」

<>はい

<第更新>


<テキスト>

戦い

説明

Yuntaishan JZ市が動作するように非常に美しいエリア、小さなX Yuntaishan夏で、彼のタスクは、風光明媚なバスを開くことです。
製のN-1道路で接続されているN、NスポットのYuntaishanスポットが、我々は、唯一の経路は、N個のスポットの間に接続されている各道路走行と必ずしも同じ経過時間があることを確認してください。
今、小さなxが車にK個体の事前に知っているだろう、と誰もがターゲットスポットとしてスポットがあり、小さなxは彼らの観光スポットに行くために皆を取る必要があります。
しかし、小型のx Kは、特定の場所への個人のどのセットを指定することができますので、彼は人々のKを送信するために、それぞれ、セットポイントからオフに設定します。
今、小さなxがポイントの最良のセットを選択収集ポイントとして各ポイントに、彼は不思議に思った、より総合的に判断するために、当然のことながら、時間を節約するためにどの思って、それがかかる最小時間はどのくらいですか?

入力形式

二つの整数N及びKの最初の行
次いで、N-1三つの整数、西、李、およびVI線、X 1は李のスポットのスポットを表す道路に接続され、この道路によって消費される時間は、Viにあるています。範囲[1..N]におけるXIと李、[1..100000]との間でVI。
次のK線、各整数紫、私が個人的に観光名所へ行く表します。

出力フォーマット

N行の後に、i行目の出力Ziを、i番目のスポット会場として表現、その光景に費やした総時間を最小限にするために、人々のKグループは行く、と小さなX用品しばらく最後の一人、彼のタスクそれは完了です。

サンプル入力

5 2 
2 5 1 
2 4 1 
1 2 2 
1 3 2 
4 
5

サンプル出力

5 
3 
7 
2 
2

解決

木を考えるのは簡単です\(DP \) およびルートを変更したいです。

可以先设计出普通的\(dp\),设\(f_{x}\)代表以\(x\)为根的子树中送完所有人并回到\(x\)的路程和。为什么要这样设计,因为这样方便转移,我们再设一个\(g_x\)代表以\(x\)为根的子树中送完所有人不回到\(x\)的路程和,就可以列出状态转移方程了。

\[f_x=\sum_{y\in son(x)}f_y+2\times e(x,y)\\g_x=f_x-\max_{y\in son(x)}\{f_y+e(x,y)-g_y\}\]

那么根据题意,答案就是\(g_x\),应该比较好理解,那么我们就得到了一个\(O(n^2)\)的做法。

\(Code:\)

#include <bits/stdc++.h>
using namespace std;
const int N = 2020;
struct edge { int ver,val,next; } e[N*2];
int n,k,t,Head[N],sum[N],tar[N],f[N],g[N];
inline void insert(int x,int y,int v) { e[++t] = (edge){y,v,Head[x]} , Head[x] = t; }
inline void input(void)
{
    scanf("%d%d",&n,&k);
    for (int i=1;i<n;i++)
    {
        int x,y,v;
        scanf("%d%d%d",&x,&y,&v);
        insert( x , y , v );
        insert( y , x , v );
    }
    for (int i=1;i<=k;i++)
    {
        int x; scanf("%d",&x);
        tar[x]++;
    }
}
inline void dfs(int x,int fa)
{
    int Max = 0; sum[x] = tar[x];
    for (int i=Head[x];i;i=e[i].next)
    {
        int y = e[i].ver;
        if ( y == fa ) continue;
        dfs( y , x );
        sum[x] += sum[y];
        if ( !sum[y] ) continue;
        f[x] += f[y] + 2 * e[i].val;
        Max = max( Max , f[y] + e[i].val - g[y] );
    }
    g[x] = f[x] - Max;
}
int main(void)
{
    freopen("kamp.in","r",stdin);
    freopen("kamp.out","w",stdout);
    input();
    for (int i=1;i<=n;i++)
    {
        memset( f , 0 , sizeof f );
        memset( g , 0 , sizeof g );
        memset( sum , 0 , sizeof sum );
        dfs( i , 0 );
        printf("%d\n",g[i]);
    }
    return 0;
}

然后直接考虑换根即可。状态\(g\)的形式是很复杂的,我们不妨进行化简,令:
\[g_x=f_x-g'_x\]
那么就有\[g_x=f_x-\max_{y\in son(x)}\{f_y+e(x,y)-g_y\}\\=f_x-\max_{y\in son(x)}\{f_y+e(x,y)-f_y+g'_y\}\\=f_x-\max_{y\in son(x)}\{e(x,y)+g'_y\}\]

又有\[g_x=\max_{y\in son(x)}\{e(x,y)+g'_y\}\]

\(ok\),事实上\(g'_x\)的意义也就是以\(x\)出发,走向\(x\)子树中最远关键点的距离,最后要的答案\(g_x=f_x+g'_x\)

我们发现\(g'_x\)很容易换根求,只需要考虑向下走的最长链和向上走的最长链,记录一下最大值和次大值来更新即可。

那么问题就转换为了求\(f_x\)。其实考虑一下路径的形态很容易得到换根方程:
\[f_y=\begin{cases}f_x& \exists\ k\in subtree(y)\\f_x-2\times e(x,y)& \forall \ k\in subtree(y)\\f_x+2\times e(x,y) & \forall \ k\not\in subtree(y) \end{cases}\]

其中\(k\)代表关键点,于是就可以\(O(n)\)求了。

\(Code:\)


#include <bits/stdc++.h>
using namespace std;
const int N = 500020;
struct edge { int ver,val,next; } e[N*2];
int n,k,t,Head[N],sum[N],tar[N],p[N],root,deg[N];
long long f[N],d[N][2],g[N],_f[N];
inline void insert(int x,int y,int v) { e[++t] = (edge){y,v,Head[x]} , Head[x] = t; }
inline void input(void)
{
    scanf("%d%d",&n,&k);
    for (int i=1;i<n;i++)
    {
        int x,y,v;
        scanf("%d%d%d",&x,&y,&v);
        insert( x , y , v );
        insert( y , x , v );
        deg[x]++ , deg[y]++;
    }
    for (int i=1;i<=k;i++)
    {
        int x; scanf("%d",&x);
        tar[x]++;
    }
}
inline void dfs(int x,int fa)
{
    sum[x] = tar[x];
    for (int i=Head[x];i;i=e[i].next)
    {
        int y = e[i].ver;
        if ( y == fa ) continue;
        dfs( y , x );
        sum[x] += sum[y];
        if ( !sum[y] ) continue;
        f[x] += f[y] + 2LL * e[i].val;
        long long val = d[y][1] + e[i].val;
        if ( val >= d[x][1] )
            d[x][0] = d[x][1] , d[x][1] = val , p[x] = y;
        else if ( val > d[x][0] ) d[x][0] = val;
    }
}
inline void dp(int x,int fa)
{
    for (int i=Head[x];i;i=e[i].next)
    {
        int y = e[i].ver;
        if ( y == fa ) continue;
        _f[y] = _f[x] - ( k - sum[y] == 0 ) * 2LL * e[i].val;
        _f[y] += 2LL * e[i].val * ( sum[y] == 0 );
        if ( p[x] != y )
            g[y] = max( g[y] , d[x][1] + e[i].val );
        else g[y] = max( g[y] , d[x][0] + e[i].val );
        g[y] = max( g[y] , g[x] + e[i].val );
        dp( y , x );
    }
}
inline void findroot(void)
{
    for (int i=1;i<=n;i++)
        if ( deg[i] == 1 )
            root = i;
}
int main(void)
{
    freopen("kamp.in","r",stdin);
    freopen("kamp.out","w",stdout);
    input();
    findroot();
    dfs( root , 0 );
    _f[root] = f[root];
    dp( root , 0 );
    for (int i=1;i<=n;i++)
        printf("%lld\n",_f[i]-max(g[i],d[i][1]));
    return 0;
}

<后记>

おすすめ

転載: www.cnblogs.com/Parsnip/p/11389569.html