code3728 联合权值

一开始暴搜,超时3个点...

后来看了题解:

 

首先,两个点的距离为2当且仅当它们都与一个点直接相连

反过来说,一个点所有的出边的终点都是互相距离2的(最大值可以依靠这个方法,前向星处理的时候将每个点的最大出点和次大出点存起来,最后过一遍比较乘积)

那么,所有点对的权值和就是每一个点所产生的点对权值和的总和

但此时,如若要对每一个点的出点进行两两配对,每一个点需要O(e^2)(e为该点出度)

只要有一个点有太多的出边就会TLE,此时我们我可以利用乘法分配律

w[i]*w[j1]+w[i]*w[j2]+...+w[i]*w[jn]=w[i]*(w[j1]+...+w[jn])

我们定义一个点的围权和为到该点距离为1的点的权值和

从这个式子中,我们可以看见:i点所产生的权值和,相当于是与i点直接相连的那些点中(j1,j2,j3...),每个点(j1,j2,j3)的围权和的和,每个围权和减去自己本身,再乘以该点权值

再利用前向星存储,这样时间复杂度会直接降到O(n)

 

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#define Size 200005
using namespace std;

int IN[Size][2];

struct node{
    int to;
    int next;
}edge[Size*2];
int head[Size];

int n;
int w[Size];
long long sum=0; int largest=-1;
int L[Size][2];
long long d[Size];

void check(int a,int b){
    int ww=w[b];
    if(ww>L[a][0]){
        L[a][1]=L[a][0];
        L[a][0]=ww;
    }else if(ww>L[a][1])L[a][1]=ww;
}

int main(){
    memset(head,-1,sizeof(head));
    freopen("3728.in","r",stdin);
    
    cin>>n;
    int a,b;
    for(int i=1;i<n;i++)scanf("%d%d",&IN[i][0],&IN[i][1]);
    for(int i=1;i<=n;i++)scanf("%d",w+i);
    for(int i=1;i<n;i++){
        a=IN[i][0]; b=IN[i][1];
        edge[i*2-1].to=b; edge[i*2-1].next=head[a]; head[a]=2*i-1;
        check(a,b); d[a]+=w[b];
        edge[i*2].to=a; edge[i*2].next=head[b]; head[b]=2*i;
        check(b,a); d[b]+=w[a];
    }
    
    for(int i=1;i<=n;i++){
        largest=max(largest,L[i][0]*L[i][1]);
    }
    for(int i=1;i<=n;i++){
        for(int j=head[i];j!=-1;j=edge[j].next){
            int x=edge[j].to;
            sum+=(w[x]*(d[i]-w[x])%10007)%10007;
            sum%=10007;
        }
    }
    
    cout<<largest<<' '<<sum<<endl;
    
    return 0;
}

 

转载于:https://www.cnblogs.com/FuTaimeng/p/5606514.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值