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

P3629 [APIO2010]巡邏-樹的直徑

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

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

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

k==1

當k==1時,發現,只要不zz一般地從自己連到自己(造的路必須通過),都是可以幫警察省下路程的,所以我們不妨考慮一下貪心地選擇,即選擇樹上距離最長的兩點,因為不管我們選擇了那兩個點,省下的距離都是兩點之間的距離 - 1(走造的那條路還要 1) 那麼我們只要找到距離最大的兩個點就可以省下最長的路程這就引出了我們的主角——樹的直徑

樹的直徑

什麼是樹的直徑呢?其實就是樹上距離相差最大的兩個點之間的路徑,概念非常好理解,程式碼也不長

怎麼樹的直徑

  1. 兩次dfs/bfs
  2. 樹形dp

兩種方法各有優缺點將在先問中講到

dfs/bfs求樹的直徑

其實就是在樹上隨機選一個點,對他進行一次dfs/bfs求出距離它距離最遠的一個點A,然後再用那一個點dfs/bfs回來找到一個離A點距離最遠的B點,AB之間的路徑即為樹的直徑

優點:可以記錄直徑的起點,重點,再配合一個dfs可以求出直徑上的每一個點

缺點:遇到負邊權的樹就GG

樹上dp求樹的直徑

通過樹形dp的方式,通過求出差不多每個點之間的距離,求最大值,聽上去比較難理解,但學過樹形dp的同學應該都知道碼量比較小

優點:可以處理負邊權的樹

缺點:真的只能求一個樹的直徑的長度,其他的求不出

兩種方法互相補充,正是這題的優秀之處,它同時考到了兩種演算法

然後當k==1時,就可以省下樹的直徑-1的路啦(顯然這是省的最多的),由於本來要走的路程是(N-1)<<1(每條路走兩次),我們當k==1時的答案就是

2*(N-1)-maxn+1//maxn為直徑長度

k==2時

這是這道題目的難點所在,但如果想通了k==1時的情況的話,其實也不是很難然而本蒟蒻還是卡了差不多一個上午我們在求出了一開始求出了樹的直徑之後,我們可以把造的那條邊先忽略掉,然而,要怎麼解決有些邊已經被省下的情況呢?

首先,我們得發現,當我們加入了一條邊後,就多了一個環這棵樹就變成了一顆基環樹,所以當我們加入第二條邊時,他會出現第二個環,這個環的情況也不多,不如再來一波分類討論

  1. 此環的邊不和第一個環有重疊
  2. 此環的邊和第一個環沒有重疊

當第二種情況時,不用想就應該知道,只要再貪心一次(把第一條直徑上的邊忽略)就可以了,所以我們真正的挑戰在第一種情況

敲黑板,以下是重點

當我們的第二個環有與第一個環有重邊時,因為造出來的路必須只經過一次,所以這個環肯定是要遍歷一次的,也就是相當於我們第一次造路並沒有幫重疊的邊省下路程,那麼怎麼處理這種情況呢??

想必各位dalao都知道,

x-y-(-y)=x

bingo!!!我們只要把第一條直徑上的邊的邊權改為 -1 (或一個你喜歡的負數) 就行啦

敲黑板,重點結束

那麼我們怎麼樣才能把直徑上的邊權改為 -1 呢?

void del(int now)
{
    while(pre[now]){
        int fa=pre[now];
        for(int i=head[fa];i;i=e[i].next){
            if(e[i].to==now){
                e[i].v=-1;
                break;
            }
        }
        for(int i=head[now];i;i=e[i].next){
            if(e[i].to==fa){
                e[i].v=-1;
                break;
            }
        }
        now=fa;
    }
}

 程式碼如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N=100001;
const int M=200001; 
int n,k,cnt,t=1,maxn,zj,ans;
bool vis[N];
queue<int> q;
struct node
{
    int next,to,v;
}e[M];
int head[N],dis[N],pre[N];
inline void add(int x,int y)
{
    e[++cnt].next=head[x];
    e[cnt].to=y;
    e[cnt].v=1;
    head[x]=cnt;
}
void bfs(int u,int pd)
{
    q.push(u);
    while(q.size())
    {
        int now=q.front();q.pop();

        for(int i=head[now];i;i=e[i].next)
        {
            int to=e[i].to;
            if(to==u||dis[to]) continue;
            dis[to]=dis[now]+1;
            if(pd)pre[to]=now;
            q.push(to);
        }
    }
}
void del(int now)
{
    while(pre[now]){
        int fa=pre[now];
        for(int i=head[fa];i;i=e[i].next){
            if(e[i].to==now){
                e[i].v=-1;
                break;
            }
        }
        for(int i=head[now];i;i=e[i].next){
            if(e[i].to==fa){
                e[i].v=-1;
                break;
            }
        }
        now=fa;
    }
}
void dp(int x)
{
    vis[x]=1;
    for(int i=head[x];i;i=e[i].next){
        int y=e[i].to;
        if(vis[y]) continue;
        dp(y);
        maxn=max(maxn,dis[x]+dis[y]+e[i].v);
        dis[x]=max(dis[x],dis[y]+e[i].v);
    }
}
void get()
{
    maxn=0;
    for(int i=1;i<=n;i++)
        if(dis[i]>maxn)
            maxn=dis[i],t=i;
}
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n-1;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        if(a==b) continue;
        add(a,b);add(b,a);
    }
    bfs(1,0);
    get();
    memset(dis,0,sizeof(dis));
    maxn=0;
    bfs(t,1);
    get();
    ans=2*n-maxn;
    if(k==1){
        printf("%d",ans-1);
        return 0;
    }
    del(t);
    memset(dis,0,sizeof(dis));
    memset(vis,0,sizeof(vis));
    maxn=0;
    dp(1);
    printf("%d",ans-maxn);
    return 0;
}