題解 P3761 TJOI2017 城市

2022-03-07 02:54:46 字數 3439 閱讀 1774

update 2020/7/15 優化了一下 \(markdown\) 的用法,增加了前面的題目描述。從加里敦大學城市規劃專業畢業的小明來到了乙個地區城市規劃局工作。這個地區一共有 \(n\) 座城市,\(n-1\) 條高速公路,保證了任意兩運城市之間都可以通過高速公路相互可達,但是通過一條高速公路需要收取一定的交通費用。小明對這個地區深入研究後,覺得這個地區的交通費用太貴。

小明想徹底改造這個地區,但是由於上司給他的資源有限,因而小明現在只能對一條高速公路進行改造,改造的方式就是去掉一條高速公路,並且重新修建一條一樣的高速公路(即交通費用一樣),使得這個地區的兩個城市之間的最大交通費用最小(即使得交通費用最大的兩座城市之間的交通費用最小),並且保證修建完之後任意兩座城市相互可達。如果你是小明,你怎麼解決這個問題?輸入資料的第一行為乙個整數 \(n\),代表城市個數。

接下來的 \(n - 1\) 行分別代表了最初的 \(n - 1\) 條公路情況。每一行都有三個整數 \(u,v,d\) 。\(u,v\) 代表這條公路的兩端城市標號,\(d\) 代表這條公路的交通費用。

$ 1 \leq u,v \leq $, $ 1\leq d \leq 2000 $。輸出資料僅有一行,乙個整數,表示進行了最優的改造之後,該地區兩城市 之間最大交通費用。

首先由 $n \leq 5000 $ ,時間限制是 \(3s\) 我們可以確定本題 \(o(n^2)\)可以卡過去。

我們就可以先有乙個 \(o(n^2)\) 的暴力解法。

(這一版基本是照著某一樓的題解打出來的)

我們列舉每一條邊斷開,然後求連個聯通塊各自的直徑,以及兩個聯通塊的最短半徑,基本可以說是半個純暴力。

void diameter(const int u)//找直徑的函式

else if(v > f[u][1])

}diameter = max(diameter,f[u][1] + f[u][0]);//很標準的乙個求樹直徑的 dp。

}void radius(const int u,const int front)//找半徑的函式

int main()

}printf("%d",ans);

return 0;

}

斷的邊一定在原來樹的直徑上,且是樹所有直徑的公共邊。

對於非直徑上的邊,就算斷掉,剩下的兩個聯通塊的直徑有乙個還是原來的直徑,所以對其我們要求的答案無影響。

然後直徑的非公共邊。

如圖樹的直徑有兩條, $ 1->8 $ 和 $ 1->9 $ ,斷掉 $ 5->6,5->7,6->9,7->8$ 中的任意一條,都不會讓剩下的兩個聯通塊的直徑減小,所以其對答案也無影響。

(這裡的性質使選原樹任意一條直徑進行刪邊都可以找到正確答案所刪的那一條邊)

由此我們可以得到乙個優化, 時間複雜度是 $ o(nl)$ , \(l\) 是原樹直徑的邊數。

void dfs(const int u,const int fa)

}void diameter(const int u)

else if(v > f[u][1])

} diameter = max(diameter,f[u][1] + f[u][0]);

}void radius(const int u,const int front)

int main() }

printf("%d",ans);

return 0;

}

從 $ 17.55s -> 1.61s $,掛了氧氣能達到 \(871ms\) 。

\(1.\) 2如果連的是直徑上的點,那麼可以確定新樹的直徑是兩個聯通塊直徑上的較長鏈相加,為了使其盡可能短,所以我們要連兩個聯通塊直徑的中點來使較長鏈更短。

\(2.\) 如果連的不是直徑上的點,那麼可以確定新樹的直徑是兩個聯通塊直徑上的較長鏈相加在加上連線點到各自直徑的距離,是一定長於 方案 \(1\) 的。

所以可以寫乙個找直徑中點的函式代替上文中找半徑的函式。

這個函式時間複雜度很難算,姑且可當做 \(\omega(1)\) ,卡一卡就變 \(o(l)\) 了。

可以證明的是聯通塊上的直徑一定有一半以上的長度是與原樹直徑重合的(只需要理解一下上文用 \(dp\) 求直徑的做法),可以用這個性質來找中點。

這個優化**我沒單獨寫

int rt=0,lt=0,half = ans>>1,cur;

cur = i;

while(dp[cur][0] - ww[cur] > half && cur) cur = mvv[cur];

rt = dp[cur][0];

cur = mv[i];

half = (f[mv[i]][0] + f[mv[i]][1])>>1;

while(f[cur][0] - w[cur]> half && cur) cur = mv[cur];

lt = f[cur][0];

ans = max(ans,w[i] + lt + rt);

調了很久也沒調出來。

我們在直徑上遍歷刪邊的時候,不難發現做了很多的重複的遍歷。

在找右邊直徑的過程都是可以通過 \(o(n)\) 預處理變成 \(o(1)\) 的。

在找左邊直徑的過程可以用 \(book\) 陣列標記,不重複遍歷,也可以實現整體 \(o(n)\)的。

最終加上連邊的優化是可以達到 \(\omega(n)\)?

需要特別注意的是,會有特殊的資料如圖:

就是如圖所示,刪去 \(6 -> 1\) 的邊後最長鏈不經過 \(1\) 點,這需要特殊處理。

即斷的邊的端點不一定在斷邊後聯通塊的直徑上。

我的想法就是先找到最長鏈的兩個端點,再分別從兩個端點跑一次 \(dfs\) 。

要記錄兩個東西。

當前子樹直徑。

據當前子樹根節點最近的直徑上的節點。

void dfs1(const int u,const int fa)

else if(v > f[u][1])

a[u] = max(a[u],a[e[i].to]);

} a[u] = max(a[u],f[u][1] + f[u][0]);

}void dfs(const int u,const int fa)

else if(v > dp[u][1])

b[u] = max(b[u],b[e[i].to]);

} b[u] = max(b[u],dp[u][1] + dp[u][0]);

}

記錄每乙個子樹的最長鏈,次長鏈,然後斷的邊移動,但是不用 \(dp\) 了,可以直接從陣列中找到當前情況下各聯通塊的直徑,最後找一下對應直徑中點就可以找到答案了。

洛谷P3761 TJOI2017 城市

從加里敦大學城市規劃專業畢業的小明來到了乙個地區城市規劃局工作。這個地區一共有ri座城市,1條高速公路,保證了任意兩運城市之間都可以通過高速公路相互可達,但是通過一條高速公路需要收取一定的交通費用。小明對這個地區深入研究後,覺得這個地區的交通費用太貴。小明想徹底改造這個地區,但是由於上司給他的資源有...

洛谷3761,TJOI2017城市

這道題一開始在想可以列舉每個點對,嘗試刪除其間的邊,因為有o n2 個點對,所以要o 1 更新答案 後來發現,因為是樹,所以只有o n 個點對是有用的 這麼顯然的結論一開始沒發現,看來還是我太弱了 然後就可以每次o n 判斷 首先定義在一棵樹 x 中,對於點y,f x,y 等於以 y 為根的有根樹,...

題解 P3758 TJOI2017 可樂

題目鏈結 樹形 dp 多半是跑不過去的,可用矩陣快速冪解決 題目大意 給定乙個無向圖,0 秒時機械人在 1 號點,每秒可以走到另乙個相鄰的點或者不動或者自爆,求 t 秒內行動方案數 矩陣快速冪 分析 首先把問題統一,不動我們連自環即可,自爆可以連單向邊到虛擬點,虛擬點連自環,然後問題就變成了 t 秒...