NOIp2014提高组
【题目描述】
无向连通图G 有n 个点,n - 1 条边。点从1 到n 依次编号,编号为 i 的点的权值为W i ,每条边的长度均为1 。图上两点( u , v ) 的距离定义为u 点到v 点的最短距离。对于图G 上的点对( u, v) ,若它们的距离为2 ,则它们之间会产生Wu
×Wv 的联合权值。
请问图G 上所有可产生联合权值的有序点对中,联合权值最大的是多少?所有联合权值之和是多少?
【输入输出格式】
输入格式:
输入文件名为link .in。
第一行包含1 个整数n 。
接下来n - 1 行,每行包含 2 个用空格隔开的正整数u 、v ,表示编号为 u 和编号为v 的点之间有边相连。
最后1 行,包含 n 个正整数,每两个正整数之间用一个空格隔开,其中第 i 个整数表示图G 上编号为i 的点的权值为W i 。
输出格式:
输出文件名为link .out 。
输出共1 行,包含2 个整数,之间用一个空格隔开,依次为图G 上联合权值的最大值
和所有联合权值之和。由于所有联合权值之和可能很大,输出它时要对10007 取余。
【输入输出样例】
输入样例#1:
5 1 2 2 3 3 4 4 5 1 5 2 3 10
输出样例#1:
20 74
【说明】
本例输入的图如上所示,距离为2 的有序点对有( 1,3) 、( 2,4) 、( 3,1) 、( 3,5) 、( 4,2) 、( 5,3) 。
其联合权值分别为2 、15、2 、20、15、20。其中最大的是20,总和为74。
【数据说明】
对于30% 的数据,1 < n≤ 100 ;
对于60% 的数据,1 < n≤ 2000;
对于100%的数据,1 < n≤ 200 , 000 ,0 < wi≤ 10, 000 。
【思路】
60%:用暴力搜索可以将解决60%的数据,如果i和j相连,j和k相连,i和k不相连,那么可以认为i和k可以构成联合最大权值,用一个O(N^3)的循环即可。
100%:
这个题AC的思路很多,可以跑树,用LCA处理两个兄弟之间的关系,也可以跑图,想要,存储一定要用前向星。(不会问度娘)。我能够AC还要感谢@yangs_s,如果不是他的博客http://blog.csdn.net/yangs_s/article/details/47359967,这个题我也做不出AC来的,我在这里把他的思路说一下吧。
有两个关键点。
首先输出分为两部分,可以把题目分成两部分来看,对一个点来说,最大的乘积自然就是和它相邻的两个权值最大的节点的权值的乘积, 读入后顺手处理一下就能出来。
第二个关键点就是加法结合律。
没错,加法结合律!
我看到神犇的博客后伏案长叹,这是小学三年级的内容,我就这样还给老师了。
我们假设和o点相邻的点有a,b,c,d,e,f,g七个,假设他们的权值就是a,b,c,d,e,f,g。某二逼青年的计算过程是这样的:sum:=a·b+a·c+a·d…….+a….+e*g….,但其实我们这样处理,我们先用加法运算法则,发现sum其实就是每个点的权值乘以可以联合(距离为2)的节点的所有权值之和,所以说,我们在读入数据的时候,可以预先处理一下,把每个节点周围相邻节点的权值之和给加起来,就以上述为例,在处理时,把a,b,c….g的权值加起来为s[o]{o点相邻点的权值之和},然后在计算时,就可以用sum:=a·(s[o]-a)+b·(s[o]-b)…..很自然的结果就出来了!
算法的时间复杂度应该是O(kn),k应该是一个很小很小远小于N的常数,应该和LCA的差不多了吧。
const ma=10007; const mx=200000; type ss=record u,v:longint; end; var s:array[1..mx] of int64;//s[i]表示和结点i所有相连的点的权值之和 e:array[1..mx] of ss;//存的是边 w,max1,max2:array[1..mx]of longint; {w[i]表示第i个点的权值 max1[i]表示和i相连得结点中权值最大的结点 max2[i]表示和i相连得结点中权值最小的结点 } n,i,j,ans1,ans2:longint; procedure box; var i,j:longint; begin fillchar(s,sizeof(s),0); fillchar(e,sizeof(e),0); fillchar(w,sizeof(w),0); fillchar(max1,sizeof(max1),0); fillchar(max2,sizeof(max2),0); ans1:=0; ans2:=0; end; //变量初始化 procedure work(x:longint;var a,b:longint); //x的值是不会被改变的,a和b是可以被改变的 begin //首先已经得出值的数组是合法的,再次更改过后也一定是合法的 if x>a then begin b:=a; a:=x; end else if x>b then b:=x; end; //用来比较大小的过程,必须用全局变量 procedure change; var i,u,v:longint; begin for i:=1 to n-1 do begin u:=e[i].u; v:=e[i].v; inc(s[u],w[v]); inc(s[v],w[u]); work(w[v],max1[u],max2[u]); work(w[u],max1[v],max2[v]); //不断更新结点周围的最大值和次大值 end; end; //预处理程序 procedure main1; var i:longint; begin for i:=1 to n do if max1[i]*max2[i]>ans1 then ans1:=max1[i]*max2[i]; end; //处理最大的联合权值 procedure main2; var i,j,u,v:longint; begin for i:=1 to n-1 do begin u:=e[i].u; v:=e[i].v; ans2:=(ans2+((s[u]-w[v])*w[v]) mod ma) mod ma; ans2:=(ans2+((s[v]-w[u])*w[u]) mod ma) mod ma; end; end; //处理权值之和 procedure init; var i,j:longint; begin readln(n); for i:=1 to n-1 do readln(e[i].u,e[i].v); for i:=1 to n do read(w[i]); end; procedure printf; begin writeln(ans1,' ',ans2); end; begin box; init; change; main1; main2; printf; end.