csp.ac_38_272题解

csp.ac_38_272题解题目链接#题目描述给定一颗n个点n−1条边的树,每条边的长度都是1,i号节点的权是$a_i$。如果三个节点两两在树上的距离都是4,那么称这三个节点构成了一个“组合”,一个“组合”的权是三个节点的权的乘积。求所有“组合”的权的和。数据范围:$$1\len\le10^{5},1\lea_

大家好,欢迎来到IT知识分享网。

题目链接

题目描述

给定一颗n个点n−1条边的树,每条边的长度都是1,i号节点的权是\(a_i\)。如果三个节点两两在树上的距离都是4,那么称这三个节点构成了一个“组合”,一个“组合”的权是三个节点的权的乘积。求所有“组合”的权的和。

数据范围:$$1\le n\le 10^{5} , 1\le a_i \le 10$$

题目分析

  • 1

    直觉上,一个组合中的点一定是这种形状(红色点):
    MUCA54LTZ4H_0`@T_Z_LK@M.png
    下面证明:
    设三个点为A,B,C。记d(x,y)为x到y的距离,记A到B与B到C的路径的重叠部分长为t。
    由于树上两点间简单路径是唯一的,所以$$d(A,C)=d(A,B)+d(B,C)-2t$$
    $$t=2$$
    Q.E.D

  • 2

    定义上图中黄点为组合的中心点。
    显然
    以一个点为中心点的所有组合的权值之和到此点距离为2的点的集合 的 三元子集 的权值之和

  • 3

    根据这道题 ,我们有O(n)的方法求出一个集合的三元子集的权值之和

  • 4

    前面的算法会将如下图的三个点视为一个组合,于是需要减去
    ZR_4B_XX_3HHD35PTR_LZGO.png

代码(没过)

  • 快读&邻接表等一堆废话

int n;
long long ans;
int pw[MAXN],head[MAXN],to[MAXN<<1],nxt[MAXN<<1],cnt;
inline void add(int a,int b){
    ++cnt;
    to[cnt]=b;
    nxt[cnt]=head[a];
    head[a]=cnt;
    return ;
}
inline int read(){
    int x=0;char c;
    c=getchar();
    while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9'){
        x=x*10+c-'0';
        c=getchar();
    }
    return x;
}
  • 求与中心点距离为2的点

    使用两个队列,算法类似bfs

for(int j=head[i];j;j=nxt[j]) q1.push(to[j]); //q1存距离为1的点
while(!q1.empty()){
      now=q1.front();q1.pop();
      for(int j=head[now];j;j=nxt[j]){
            if(to[j]==i) continue;
            q2.push(to[j]); //q2存距离为2的点
      }
}
  • 求三元组的和

    此处可以参考这篇题解

for(c=1;!q2.empty();++c){
            s[0][c]=pw[q2.front()];q2.pop(); //把点从队列里取到数组里
        }
        --c;
        s[1][0]=s[2][0]=s[3][0]=0;
        for(int i=1;i<=c;++i) s[1][i]=s[1][i-1]+s[0][i]; //一元组的和 前缀和
        for(int i=1;i<=c;++i) s[2][i]=s[2][i-1]+s[1][i-1]*s[0][i]; //二元组的和
        for(int i=1;i<=c;++i) s[3][i]=s[3][i-1]+s[2][i-1]*s[0][i];
  • 总代码

#include<iostream>
#include<queue>
#define MAXN 100005
using namespace std;
int n;
long long ans;
int pw[MAXN],head[MAXN],to[MAXN<<1],nxt[MAXN<<1],cnt;
inline void add(int a,int b){
    ++cnt;
    to[cnt]=b;
    nxt[cnt]=head[a];
    head[a]=cnt;
    return ;
}
inline int read(){
    int x=0;char c;
    c=getchar();
    while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9'){
        x=x*10+c-'0';
        c=getchar();
    }
    return x;
}
queue<int> q1,q2;
int s[4][MAXN];
int main(){
    n=read();
    for(int i=1;i<=n;++i) pw[i]=read();
    int a,b;
    for(int i=1;i<n;++i){
        a=read(),b=read();
        add(a,b);
        add(b,a);
    }
    int c,now;
    for(int i=1;i<=n;++i){
        while(!q2.empty()) q2.pop();
        for(int j=head[i];j;j=nxt[j]) q1.push(to[j]);
        while(!q1.empty()){
            now=q1.front();q1.pop();
            for(int j=head[now];j;j=nxt[j]){
                if(to[j]==i) continue;
                q2.push(to[j]);
                //cout<<"!"<<to[j]<<endl;
            }
            //cout<<"!"<<now<<endl;
        }
        for(c=1;!q2.empty();++c){
            s[0][c]=pw[q2.front()];q2.pop();
            //cout<<"?"<<s[0][i]<<endl;
        }
        --c;
        s[1][0]=s[2][0]=s[3][0]=0;
        for(int i=1;i<=c;++i) s[1][i]=s[1][i-1]+s[0][i]; //一元组的和 前缀和
        for(int i=1;i<=c;++i) s[2][i]=s[2][i-1]+s[1][i-1]*s[0][i]; //二元组的和
        for(int i=1;i<=c;++i) s[3][i]=s[3][i-1]+s[2][i-1]*s[0][i];
        //ans+=/*q2的三元组的和*/;
        ans+=s[3][c];
        //cout<<ans<<' '<<c<<endl;
    }
    cout<<ans;
    return 0;
}

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/32156.html

(0)

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

关注微信