1. 程式人生 > >NOIP提高組2018 D1T3 【賽道修建】

NOIP提高組2018 D1T3 【賽道修建】

頹了好幾天,終於把這到題處理了一下。

話說,其實我考場上想出正解了,但是手殘,算複雜度的時候多按了一個零,導致算出來是1億多的複雜度,都不敢打。。。就把部分分都撿了一下。。。

題目描述:

C 城將要舉辦一系列的賽車比賽。在比賽前,需要在城內修建 m 條賽道。

C 城一共有 n 個路口,這些路口編號為 1,2,…,n1,有 n1 條適合於修建賽道的雙向通行的道路,每條道路連線著兩個路口。其中,第 ii 條道路連線的兩個路口編號為 ai 和 bi,該道路的長度為 li。藉助這 n-1n1 條道路,從任何一個路口出發都能到達其他所有的路口。

一條賽道是一組互不相同的道路 e1,e2,…,ek滿足可以從某個路口出發,依次經過 道路 e1,e2,…,ek(每條道路經過一次,不允許調頭)到達另一個路口。一條賽道的長度等於經過的各道路的長度之和。為保證安全,要求每條道路至多被一條賽道經過。

目前賽道修建的方案尚未確定。你的任務是設計一種賽道修建的方案,使得修建的 m 條賽道中長度最小的賽道長度最大(即 m 條賽道中最短賽道的長度儘可能大)

思路分析:

從部分分開始講吧。

菊花圖:

這個部分分還是挺好拿的,把邊從大到小排序之後,直接二分答案。兩個指標,i,正著來,j,倒著來,i就順著取下去,對於每個i給它匹配一個最大的相加剛好大於二分值的j(一開始就大於二分值的要特殊處理),注意到這個決策是有單調性的,即:當i1小於i2時,那麼相匹配的j1一定大於等於j2

,這樣的話,我們每次只要掃一遍就能得到答案(考完後有人和我說求菊花圖要log2二分答案套二分,嗯,就是我們的zyk同志,其實我一直都覺得這個人的腦回路特別清奇。。。)。

分支不超過3:

這個的話其實就是正解的簡化版了,考慮二分答案加樹形DP。

  dp[u]:表示以u為根的子樹中有多少長度大於mid的鏈。

  f[u]:表示以u為根的子樹中,除去那些被統計到dp陣列中的鏈,現在能傳到父親的鏈的長度最長的鏈的長度(貌似有點拗口呢。。。)。

轉移應該很簡單了吧。

首先,dp[u]=∑v∈u's sons 如果u的兩個兒子的最長鏈加起來大於mid,那麼再給dp[u]+1,f[u]=0,否則的話,f[u]=max(f[v1

],f[v2])。

正解:

正解其實就是把兩者結合一下啦。

但是特別注意的就是:我們要在滿足匹配數最大的情況下,找一條最長的鏈傳給父親。

那麼這個要求我們應該怎麼來實現呢?——二分嘛!

我們可以二分我們需要傳上去的那條鏈,然後在匹配的時候跳過這條鏈就行了。(其實這是成一類dalao教我的)

程式碼實現:(沒想到吧,我轉C++辣!!!)

(不得不說一句——C++壓行是真的爽,啊哈哈哈!!!)

#include <bits/stdc++.h>
using namespace std;
const int maxn=50005;
int dp[maxn],f[maxn],head[maxn],nxt[2*maxn],vet[2*maxn],dist[2*maxn];
int tot,mid;
void add(int x,int y,int z){
    tot++;
    nxt[tot]=head[x];
    vet[tot]=y;
    head[x]=tot;
    dist[tot]=z;
}
bool cmp(int a,int b) {
    return a>b;
}
void dfs(int u,int father){
    vector<int> a;
    for (int i=head[u];i;i=nxt[i]){
        int v=vet[i];
        if (v==father) continue;
        dfs(v,u);
        f[v]+=dist[i];
        if (f[v]<mid) a.push_back(f[v]); else dp[u]++;
        dp[u]+=dp[v];
    }
    int l=0,r=a.size()-1;
    sort(a.begin(),a.end(),cmp);
    a.push_back(0);
    int ans=a.size()-1,Max=0,i=0,j=a.size()-2;
    while (i<j) if (a[i]+a[j]>=mid) i++,j--,Max++; else j--;
    while (l<=r){
        int now=(l+r)>>1,res=0,i=0,j=a.size()-2;
        while (i<j){
            if (i==now) {i++; continue;}
            if (j==now) {j--; continue;}
            if (a[i]+a[j]>=mid) i++,j--,res++; else j--;
        }
        if (res>=Max) Max=res,ans=now,r=now-1; else l=now+1;
    }
    dp[u]+=Max; f[u]=a[ans];
}
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for (int i=1;i<n;++i){
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z); add(y,x,z);
    }
    int l=1,r=500000000,ans=0;
    while (l<=r){
        memset(dp,0,sizeof(dp));
        memset(f,0,sizeof(f));
        mid=(l+r)>>1;
        dfs(1,0);
        if (dp[1]>=m) ans=mid,l=mid+1; else r=mid-1;
    }
    printf("%d\n",ans);
    return 0;
}