5055. 树上路径

题目大意

给定一颗 n 个结点的无根树,每个点有一个点权,定义一条路径的价值为路径上的点权和-路径的点权最大值。
给定参数p,求有多少不同的树上简单路径,满足它的价值恰好是 p 的倍数。

Data Constraint
n105,p107

题解

考虑点分治。
对于当前的分治重心,把所有以它为起点的路径取出来,按照路径上点权的最大值排序。然后考虑如何计算答案。对于当前枚举到的第 i 条路径,前面i1条路径的最大值显然都小于等于当前这条,所以可以直接维护一个桶,每次在桶内查询即可。
注意要去掉一个子树内部的路径对答案的贡献。

时间复杂度: O(nlog2n)

SRC

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std ;

#define N 100000 + 10
#define M 10000000 + 10
typedef long long ll ;
struct Note {
    int Maxv , Sum ;
    Note ( int X = 0 , int Y = 0 ) { Maxv = X , Sum = Y ; }
} D[N] , tp[N] ;

bool vis[N] ;
int Node[2*N] , Next[2*N] , Head[N] , tot ;
int val[N] , Size[N] , Maxs[N] , T[M] ;
int n , MO ;
int Root , All , Minv , Cnt , Num ;
ll ans ;

bool cmp( Note a , Note b ) { return a.Maxv < b.Maxv ; }

void link( int u , int v ) {
    Node[++tot] = v ;
    Next[tot] = Head[u] ;
    Head[u] = tot ;
}

void GetSize( int x , int F ) {
    Size[x] = Maxs[x] = 1 ;
    for (int p = Head[x] ; p ; p = Next[p] ) {
        if ( Node[p] == F || vis[Node[p]] ) continue ;
        GetSize( Node[p] , x ) ;
        Size[x] += Size[Node[p]] ;
        Maxs[x] = max( Maxs[x] , Size[Node[p]] ) ;
    }
}

void GetRoot( int x , int F ) {
    Maxs[x] = max( Maxs[x] , Size[All] - Maxs[x] ) ;
    if ( Maxs[x] < Minv ) Minv = Maxs[x] , Root = x ;
    for (int p = Head[x] ; p ; p = Next[p] ) {
        if ( Node[p] == F || vis[Node[p]] ) continue ;
        GetRoot( Node[p] , x ) ;
    }
}

void DFS( int x , int F , int Maxv , int Sum ) {
    D[++Cnt] = Note( Maxv , Sum ) ;
    tp[++Num] = Note( Maxv , Sum ) ;
    if ( (((Sum + val[Root]) % MO - max( Maxv , val[Root] )) % MO + MO) % MO == 0 ) ans ++ ;
    for (int p = Head[x] ; p ; p = Next[p] ) {
        if ( Node[p] == F || vis[Node[p]] ) continue ;
        DFS( Node[p] , x , max( Maxv , val[Node[p]] ) , (Sum + val[Node[p]]) % MO ) ;
    }
}

void Calc( Note *a , int n , int sig ) {
    sort( a + 1 , a + n + 1 , cmp ) ;
    for (int i = 1 ; i <= n ; i ++ ) {
        if ( a[i].Maxv <= val[Root] ) ans += sig * T[MO-a[i].Sum] ;
        else {
            int s = (((a[i].Maxv - val[Root]) % MO - a[i].Sum) % MO + MO) % MO ;
            ans += sig * T[s] ;
        }
        T[a[i].Sum] ++ ;
    }
    for (int i = 1 ; i <= n ; i ++ ) T[a[i].Sum] -- ;
}

void Solve( int x ) {
    Root = All = x , Minv = 0x7FFFFFFF ;
    GetSize( x , 0 ) ;
    GetRoot( x , 0 ) ;
    vis[Root] = 1 ;
    Cnt = Num = 0 ;
    for (int p = Head[Root] ; p ; p = Next[p] ) {
        if ( vis[Node[p]] ) continue ;
        Num = 0 ;
        DFS( Node[p] , Root , val[Node[p]] , val[Node[p]] % MO ) ;
        Calc( tp , Num , -1 ) ;
    }
    Calc( D , Cnt , 1 ) ;
    for (int p = Head[Root] ; p ; p = Next[p] ) {
        if ( vis[Node[p]] ) continue ;
        Solve( Node[p] ) ;
    }
}

int main() {
    freopen( "path.in" , "r" , stdin ) ;
    freopen( "path.out" , "w" , stdout ) ;
    scanf( "%d%d" , &n , &MO ) ;
    for (int i = 1 ; i < n ; i ++ ) {
        int u , v ;
        scanf( "%d%d" , &u , &v ) ;
        link( u , v ) , link( v , u ) ;
    }
    for (int i = 1 ; i <= n ; i ++ ) scanf( "%d" , &val[i] ) ;
    Solve(1) ;
    printf( "%lld\n" , ans + n ) ;
    return 0 ;
}

以上.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值