洛谷P2986
阿新 • • 發佈:2018-12-13
簡化版題意:給一個邊點都帶權的樹,求這棵樹的重心到所有點的邊權點權和。
一般題意:
有個 n 個牧場,每個牧場有 A_i個奶牛,不同牧場之間有距離,求以某一個點為一次聚會的舉辦地,使得其他所有點到這個點花費最少(花費等同於 一個節點到這個節點的距離 * 一個節點內的奶牛數目)
首先我們考慮一下 樹的重心這一概念,現在有一個直接結論,邊為無向邊的樹的邊權對樹中心的位置不會造成影響。
聽上去可能感覺有點不可思議,但是事實就是邊權不會影響樹中心位置,因為邊是無向邊,我們可以想幾個極端例子
假設 A-B之間邊權 為 INF 雖然看似其他點到 A 的距離變遠了,但是 A 到其他點的距離也變遠了, 所以樹的重心和邊並沒有太大關係,有了這個結論我們就很好去搞了。。。下邊是某部落格上寫的定義
- 樹的重心:也叫樹的質心。找到一個點,其所有的子樹中最大的子樹節點數最少,那麼這個點就是這棵樹的重心,刪去重心後,生成的多棵樹儘可能平衡。
暫時拋去邊不看,我們可以設定三個東西,一個儲存節點裡的奶牛數,另一儲存本節點子樹上的奶牛數總和,最後一個自然就是儲存我們的dp,來記錄一下該點所有的子樹中最大的子樹節點數,最後根據 dp 來決定以誰作為重心,跑一邊簡單的最短路。。一路上記錄權值和就好了。。(因為這裡我們可以採用分層的思路,所以最短路在樹裡還是很好跑的)
以下是 AC 程式碼
#include<iostream> #include<cstdio> #include<cstring> using namespace std; #define ll long long int const int maxn = 1e6+5; struct node { ll nxt; ll to; ll val; }ed[maxn]; ll head[maxn],tot,sum; void add(ll u,ll v,ll w) { ed[++tot].to = v; ed[tot].nxt = head[u]; ed[tot].val = w; head[u] = tot; } ll dp[maxn]; ll num[maxn]; ll ans[maxn]; void dfs(ll s,ll p) { num[s] = ans[s]; for(ll i=head[s];~i;i=ed[i].nxt) { ll to = ed[i].to; if(to != p) { dfs(to, s); num[s] += num[to];//點s為根的子樹的結點個數 dp[s] = max(dp[s], num[to]);//這個記錄最大子樹節點數 } } dp[s] = max(dp[s], sum - num[s]);//最後比較一下父節點 } void spfa(ll s,ll p) { for(ll i=head[s];~i;i=ed[i].nxt) { ll to = ed[i].to; if(to != p) { num[to] = num[s] + ed[i].val; spfa(to, s); } } } ll n; int main() { scanf("%d",&n); sum = 0; tot = 0; memset(head,-1,sizeof head); for(ll i=1;i<=n;i++) { scanf("%lld",&ans[i]); sum += ans[i]; } for(ll i=1;i<n;i++) { ll x,y,z; scanf("%lld%lld%lld",&x,&y,&z); add(x,y,z); add(y,x,z); } dfs(1, 1); ll id = 1; for(ll i=2;i<=n;i++) { if(dp[i] < dp[id]) id = i; } num[id] = 0; spfa(id, id); ll ant = 0; for(ll i=1;i<=n;i++) { ant += num[i] * ans[i]; } printf("%lld\n",ant); return 0; }