1. 程式人生 > >[APIO2010]巡邏-樹的直徑

[APIO2010]巡邏-樹的直徑

題目描述

在一個地區中有 n 個村莊,編號為 1, 2, ..., n。有 n – 1 條道路連線著這些村 莊,每條道路剛好連線兩個村莊,從任何一個村莊,都可以通過這些道路到達其 他任一個村莊。每條道路的長度均為 1 個單位。 為保證該地區的安全,巡警車每天要到所有的道路上巡邏。警察局設在編號 為 1 的村莊裡,每天巡警車總是從警察局出發,最終又回到警察局。 下圖表示一個有 8 個村莊的地區,其中村莊用圓表示(其中村莊 1 用黑色的 圓表示),道路是連線這些圓的線段。為了遍歷所有的道路,巡警車需要走的距 離為 14 個單位,每條道路都需要經過兩次。

為了減少總的巡邏距離,該地區準備在這些村莊之間建立 K 條新的道路, 每條新道路可以連線任意兩個村莊。兩條新道路可以在同一個村莊會合或結束 (見下面的圖例(c))。 一條新道路甚至可以是一個環,即,其兩端連線到同一 個村莊。 由於資金有限,K 只能是 1 或 2。同時,為了不浪費資金,每天巡警車必須 經過新建的道路正好一次。 下圖給出了一些建立新道路的例子:

在(a)中,新建了一條道路,總的距離是 11。在(b)中,新建了兩條道路,總 的巡邏距離是 10。在(c)中,新建了兩條道路,但由於巡警車要經過每條新道路 正好一次,總的距離變為了 15。 試編寫一個程式,讀取村莊間道路的資訊和需要新建的道路數,計算出最佳 的新建道路的方案使得總的巡邏距離最小,並輸出這個最小的巡邏距離。

輸入輸出格式

輸入格式:

 

第一行包含兩個整數 n, K(1 ≤ K ≤ 2)。接下來 n – 1 行,每行兩個整數 a, b, 表示村莊 a 與 b 之間有一條道路(1 ≤ a, b ≤ n)。

 

輸出格式:

 

輸出一個整數,表示新建了 K 條道路後能達到的最小巡邏距離。

 

輸入輸出樣例

輸入樣例#1:
8 1 
1 2 
3 1 
3 4 
5 3 
7 5 
8 5 
5 6 
輸出樣例#1:
11
輸入樣例#2: 
8 2 
1 2 
3 1 
3 4 
5 3 
7 5 
8 5 
5 6 
輸出樣例#2: 
10
輸入樣例#3: 
5 2 
1 2 
2 3 
3 4 
4 5 
輸出樣例#3: 
6

說明

10%的資料中,n ≤ 1000, K = 1;

30%的資料中,K = 1;

80%的資料中,每個村莊相鄰的村莊數不超過 25;

90%的資料中,每個村莊相鄰的村莊數不超過 150;

100%的資料中,3 ≤ n ≤ 100,000, 1 ≤ K ≤ 2。

 

思路:

當k=1時,這道題只需要求樹的直徑(即樹上的最長路徑),相當於省掉了樹的直徑-1(新建的道路需走一次)條路徑,答案為(n-1)<<1-樹的直徑+1

樹的直徑的求法不詳細說明了,Luogu的第二位dalao的題解十二分的透徹(https://www.luogu.org/problemnew/solution/P3629)

然後當k=2時,你會發現求兩個樹的直徑可能會有覆蓋的情況,所以你只需要將這條路的值由1改為-1(如果選擇被兩個環都覆蓋路徑,則他在第一個環內無法減少路徑,第二個環亦然,故需要-1(即路徑+2))

然後就在求一個樹的直徑就好了(如果兩個環由重疊也已處理完畢)

不管這個直徑需要用樹狀DP來做(dfs不能處理負權邊)

上程式碼

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define rep(i,a,b) for(long long i=a;i<=b;i++)
using namespace std;
typedef long long ll;
ll n,k,en,to[200500],tot,val[200500],nxt[200500],fir[100500],maxn,D1,st,dis[200500];
bool book[200050];
void ade(ll u,ll v){
    to[++tot]=v;
    nxt[tot]=fir[u];
    fir[u]=tot;
    val[tot]=1;
}
void dfs(ll x,ll fa,ll s){
    if(s>maxn){maxn=s; en=x;}
    for(ll k=fir[x];k;k=nxt[k])
        if(to[k]!=fa) dfs(to[k],x,s+1);
}
bool dfs2(ll x,ll fa,ll s){
    if(s==D1 && x==en){book[x]=1; return 1;}
    for(ll k=fir[x];k;k=nxt[k])
        if(to[k]!=fa) if(dfs2(to[k],x,s+1)){book[x]=1; return 1;}
    return 0;
}
void Tree_DP(ll x,ll fa){ //樹上求最長鏈 
    for(ll k=fir[x];k;k=nxt[k]){
        if(to[k]!=fa){
            Tree_DP(to[k],x);
            maxn=max(maxn,dis[x]+dis[to[k]]+val[k]);
            dis[x]=max(dis[x],dis[to[k]]+val[k]);
        }
    }
}
int main(){
    scanf("%lld%lld",&n,&k);
    rep(i,1,n-1){
        ll u,v;
        scanf("%lld%lld",&u,&v);
        ade(u,v);ade(v,u);
    }
    dfs(1,0,0); maxn=0; st=en;
    dfs(st,0,0); D1=maxn;
    if(k==1){
        printf("%lld",((n-1)<<1)-D1+1);
        return 0;
    }
    book[0]=dfs2(st,0,0);
    rep(i,1,n)
        if(book[i])
            for(ll k=fir[i];k;k=nxt[k]) 
                if(book[to[k]]) val[k]=-1;
    maxn=0;
    Tree_DP(1,0);
    printf("%lld\n",(n<<1)-D1-maxn);
    return 0;
}