NOIP2014 联合权值 - 图的遍历 - 转化思想

两个问题都需要按照每个节点的相邻点来思考解法。
把符合题目要求的,可产生联合权值的有序点对,称为联合点

  • 第一问
    每个节点的子节点(相邻点)之间,彼此组成联合点,具体看图。

    可以比较每个节点的相邻点的权值,得到最大子节点和次大子节点。
    他们的联合权值就是这个节点所能发现的最大联合权值。
    然后更新答案

\[ans=max(ans, max1*max2) \]

  • 第二问
    直接两两相乘的话能得70分,剩下三个点会TLE
    但其实我们需要的只是乘积的和
    网上有加法分配律的算法,但我这里是难一点的...
    设一个点u的相邻点为a, b, c
    那么这个点能为答案贡献的值就是这三个点彼此的联合权值之和的两倍
    (联合权值是重复的,如(1,3)和(3,1)都要算上)
    我们要求的就是\(2(ab+bc+ac)\)
    这个值可以转化为:

\[(a+b+c)^2 = a^2+b^2+c^2 + 2ab+2bc+2ac \]

\[2(ab+bc+ac)=(a+b+c)^2-(a^2+b^2+c^2) \]

\[ans=(\sum_{i=1}^n i_{son})^2-\sum_{i=1}^n (i_{son})^2 \]

还要注意 如果不开long long 就要边加边模

#include <cstdio>
#include <algorithm>
const int maxn = 200010;
int tot=0,last[maxn],value[maxn];
bool vis[maxn];
int max1,max2,u,v,que,n,ans;
struct Edge{
    int u,v,to;
    Edge(){}
    Edge(int u, int v, int to) : u(u), v(v), to(to) {}
}e[maxn*2+10];
void addedge(int u,int v) {
    e[++tot] = Edge(u,v,last[u]);
    last[u] = tot;
}
int main() {
    scanf("%d",&n);
    for(int i=1; i<=n-1; i++){
        scanf("%d%d",&u,&v);
        addedge(u,v);
        addedge(v,u);
    }
    for(int i=1; i<=n; i++)
        scanf("%d",&value[i]);
    for(int u=1; u<=n; u++) {
        int s1=0,s2=0,max1=0,max2=0;
        for(int i=last[u]; i; i=e[i].to) {
            int v = e[i].v;
            if(value[v] > max1)
                max1 = value[v];
            else if(value[v] > max2)
                max2 = value[v];
            s1 = (s1%10007 + value[v]%10007) % 10007;
            s2 = (s2%10007 + value[v]*value[v]%10007) % 10007;    
        }
        ans = (ans%10007 + (s1*s1 - s2)%10007) % 10007;       
        que = std::max(que,max1*max2);      
    }
    printf("%d %d",que,ans%10007);
    return 0;
}
posted @ 2018-10-28 21:27  Zolrk  阅读(165)  评论(0编辑  收藏  举报