1. 程式人生 > >[JZOJ 5906] [NOIP2018模擬10.15] 傳送門 解題報告(樹形DP)

[JZOJ 5906] [NOIP2018模擬10.15] 傳送門 解題報告(樹形DP)

nor dos color 樹形dp getchar() 傳送門 noip 轉化 每一個

題目鏈接:

https://jzoj.net/senior/#contest/show/2528/2

題目:

8102年,Normalgod在GLaDOS的幫助下,研制出了傳送槍。但GLaDOS想把傳送槍據為己有,於是把Normalgod扔進了一間實驗室。這間實驗室是一棵有n個節點的樹。現在Normalgod在一號節點,出口也在一號節點,但為了打開它,必須經過每一個節點按下每個節點的開關,出口才能打開。GLaDOS為了殺死Normalgod,開始在實驗室裏釋放毒氣,因此Normalgod必須盡快逃出這間實驗室。
當然,Normalgod手中的傳送槍是可以使用的。傳送槍可以發射出兩個顏色不同的傳送門。Normalgod可以從其中一個傳送到另一個。盡管傳送槍可以在視野範圍內的任何一個經過特殊處理的表面打開一扇傳送門,但這間實驗室的設計使得Normalgod只能在他所處的房間內打開一個傳送門。 在已經存在了一個同顏色的傳送門時,打開新的傳送門會使與它同顏色的舊門消失。傳送和打開傳送門所需時間為0。


顯然,利用傳送槍會讓Normalgod更快解決謎題,可Normalgod死在了按下最後一個按鈕的路上。盡管如此,GLaDOS還是很想知道到底Normalgod最快能用多久逃出去,這對她的實驗室設計方法論有重要的指導作用。作為GLaDOS的算法模塊,你要完成這個任務。本題時限為2000ms

題解:

(聲明:題解來自JZOJ)

1.易知:每條邊至多經過2次。

技術分享圖片

如圖:若將傳送門置於fa處,則fa->u經過3次;

實際上若將傳送門置於u處,則fa->u經過2次,更優。

2.那麽最長距離總權值*2,那麽我們只需要使經過一次的邊的總和更大,

則長度=總權值*2-選用的單邊權值之和。

3.由此我們可以采用動態規劃來求解。

dp[i][0/1]

0表示在i點及i點兒子設傳送門所能得到的最大總和

1 表示不在i點及i點兒子設傳送門所能得到的最大總和

首先,對於dp[i][1]的情況,一定存在i點的祖先中有傳送門,這樣才能使結果更優。所以對於他的每一個兒子都能跳回到他的祖先。但實際上只有使一個兒子跳回祖先時,才能保證fa->i的邊經過兩次。

否則轉化為dp[i][0]的情況。則我們要使選的兒子最優。

$dp[i][1]=max(dp[vi][1]+wi)$

其次,對於dp[i][0]的情況,在每個兒子中設立傳送門並不會影響到其他兒子,因為總能從兒子回到i時再在i重設傳送門。所以我們就取每個兒子設與不設的最優值之和。

$dp[i][0]=max(dp[vi][0],dp[vi][1]+wi)$

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;

const int N=1e6+15;
int n,tot;
int head[N];
ll ans; 
ll dp[N][2];
struct EDGE
{
    int to,nxt;
    ll w;
}edge[N<<1];
inline ll read()
{
    char ch=getchar();
    int s=0,f=1;
    while (ch<0||ch>9) {if (ch==-) f=-1;ch=getchar();}
    while (ch>=0&&ch<=9) {s=(s<<3)+(s<<1)+ch-0;ch=getchar();}
    return s*f;
}
void add(int u,int v,ll w) 
{    
    edge[++tot]=(EDGE){v,head[u],w};
    head[u]=tot;
}
void dfs(int x,int pre)
{
    for (int i=head[x];i;i=edge[i].nxt)
    {
        int y=edge[i].to;
        if (y==pre) continue;
        dfs(y,x);
        dp[x][1]=max(dp[x][1],dp[y][1]+edge[i].w);//不設傳送門 
        dp[x][0]+=max(dp[y][0],dp[y][1]+edge[i].w);//設傳送門 
    }
}
int main()
{
    freopen("portal.in","r",stdin);
    freopen("portal.out","w",stdout);
    n=read();
    ll w;
    for (int i=1,u,v;i<n;i++)
    {
        u=read();v=read();w=read();
        add(u,v,w);add(v,u,w);ans+=w<<1;
    }
    dfs(1,-1);
    ans-=max(dp[1][0],dp[1][1]);
    printf("%lld\n",ans);
    return 0;
}

[JZOJ 5906] [NOIP2018模擬10.15] 傳送門 解題報告(樹形DP)