1. 程式人生 > >【noip 2015】聯合權值

【noip 2015】聯合權值

題目描述

無向連通圖G 有n 個點,n - 1 條邊。點從1 到n 依次編號,編號為 i 的點的權值為W i ,每條邊的長度均為1 。圖上兩點( u , v ) 的距離定義為u 點到v 點的最短距離。對於圖G 上的點對( u, v) ,若它們的距離為2 ,則它們之間會產生Wu×Wv 的聯合權值。

請問圖G 上所有可產生聯合權值的有序點對中,聯合權值最大的是多少?所有聯合權值之和是多少?

輸入格式:

第一行包含1 個整數n 。
接下來n - 1 行,每行包含 2 個用空格隔開的正整數u 、v ,表示編號為 u 和編號為v 的點之間有邊相連。
最後1 行,包含 n 個正整數,每兩個正整數之間用一個空格隔開,其中第 i 個整數表示圖G 上編號為i 的點的權值為W i 。

輸出格式:

輸出共1 行,包含2 個整數,之間用一個空格隔開,依次為圖G 上聯合權值的最大值和所有聯合權值之和。由於所有聯合權值之和可能很大,輸出它時要對10007 取餘。

資料範圍:
對於100%的資料,1 < n≤ 200 , 000 ,0 < wi≤ 10, 000

一道圖論題。。。

題目給定的是一顆樹,所以我一開始的想法是對於一個節點,它的父節點和任意一個子節點都能產生聯合權值,而它的每兩個子節點又可以產生聯合權值。

注意,題目裡說是“有序對”,所以我們可以只往後找,最後乘2即可。

顯然,最大聯合權值就是每個節點的父節點和子節點中最大的兩個的乘積取max。而總的聯合權值就是每個點的子節點兩兩相乘再分別於父節點相乘的和。。。

好麻煩的說!

就是!明明子節點和父節點沒什麼區別,非要劃分出來不是自找麻煩嗎!

別看到樹就dfs就父節點子節點啊喂!(使勁拍拍腦子)

直接遍歷每個點,對於每個點,遍歷它能一步到達的點,找出兩個最大的相乘,看看乘積能否更新Max。

對於總的聯合權值,不難推出這樣一個式子:

sum=a(b+c+d+e+…)+b(c+d+e+…)+c(d+e+…)…(因為只往後找)

把式子倒過來,每一項就可以看做當前點的權值和它前面所有點的權值和的乘積。

程式碼如下:

#include<cstdio>
#include<iostream>
using namespace std;
struct node{
    int
to,next; }mem[400001]; int size=0; int head[200001]={0}; int sum=0,Max=-1e9; int w[200001]; void add(int from,int to) { size++; mem[size].to=to; mem[size].next=head[from]; head[from]=size; } void work(int x) { int Max1=0,Max2=0,bef=0; for(int i=head[x];i;i=mem[i].next) { int v=mem[i].to; if(w[v]>Max1) { Max2=Max1; Max1=w[v]; } else if(w[v]>Max2) Max2=w[v]; sum+=bef*w[v]; //bef是前面的點的權值和 sum%=10007; bef+=w[v]; bef%=10007; //bef也要%10007!!否則前面bef*w[i]可能爆int } Max=max(Max,Max1*Max2); } int main() { int n; scanf("%d",&n); for(int i=1;i<n;i++) { int u,v; scanf("%d%d",&u,&v); add(u,v); add(v,u); } for(int i=1;i<=n;i++) scanf("%d",&w[i]); for(int i=1;i<=n;i++) { work(i); } printf("%d %d",Max,sum*2%10007); return 0; }