HDU 4670 Cube number on a tree ( 树的点分治 )

题意 : 给你一棵树 。 树的每一个结点都有一个权值 。 问你有多少条路径权值的乘积是一个全然立方数 。

题目中给了你 K 个素数 ( K <= 30 ) , 全部权值都能分解成这k个素数

思路 : 一个全然立方数的素因子个数都是三的倍数 , 所以我们仅仅要求各个素数的个数即可了 , 而且我们仅仅关心个数对三的余数

所以我们能够用一个 长整形来表示每一个结点到根的各个素因子的个数( 三进制压缩 ) 。

只是由于用位运算会快一点 , 所以我用了四进制。即每两位表示一个素因子的个数 。

中间合并的时候计算刚好相加之后变成0的数,然后用map统计就能够了

注意 : 题目中 pi 的值 , 0 是取不到的(  纠结了好久该怎么处理 = = )

#include <stdio.h>
#include <string.h>
#include <map>
#include <algorithm>
#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;

#define MAXN 50005
#define INF 0x3f3f3f3f
__int64 prime[35] ;
int n , k ;

struct Tree{
    struct Edge{
        int to , nex ;
        Edge(){}
        Edge( int _to  , int _nex ) {
            to = _to ;
            nex = _nex ;
        }
    }edge[MAXN*2] ;  

    int head[MAXN] ;
    int Index ;  

    void init(){
        memset( head , -1 , sizeof(head) ) ;
        Index = 0 ;
    }  

    void add( int from , int to ) {
        edge[Index] = Edge( to , head[from] ) ;
        head[from] = Index ++ ;
    }
} tree ;

inline int get( __int64 val , int pos ) {
    return ( val >> ( pos << 1 ) ) & 3 ;
}

inline void set( __int64 & val , int pos , int k ) {
    val |= ( (__int64)k << ( pos << 1 ) ) ;
}

__int64 add( __int64 a , __int64 b ) {
    __int64 ans = 0 ;
    for( int i = 0 ; i < k ; i ++ ) {
        int aa = get( a , i ) ;
        int bb = get( b , i ) ;
        aa += bb ;
        aa %= 3 ; ;
        set( ans , i , aa ) ;
    }
    return ans ;
}

__int64 fan( __int64 a  ) {
    __int64 ans = 0 ;
    for( int i = 0 ; i < k ; i ++ ) {
        int tmp = get( a , i ) ;
        tmp =  3 - tmp ;
        set( ans , i , tmp % 3 ) ;
    }
    return ans ;
}

bool vis[MAXN] ;
__int64 val[MAXN] ;
__int64 ans ;

// 第i结点为根的子树的最大个数
int dp[MAXN] ;
// 第i结点为根的子树的大小
int sum[MAXN] ;  

int Sum ;
int Min , Minid ;  

void dfs1( int u , int f ) {
    sum[u] = 1 ;
    dp[u] = 0 ;
    for( int i = tree.head[u] ; ~i ; i = tree.edge[i].nex ) {
        int ch = tree.edge[i].to ;
        if( ch == f || vis[ch] ) continue ;
        dfs1( ch , u  ) ;
        sum[u] += sum[ch] ;
        dp[u] = max( dp[u] , sum[ch] ) ;
    }
}  

void dfs2( int u , int f ) {
    int M = max( dp[u] , Sum - sum[u] ) ;
    if( M < Min ) {
        Min = M ;
        Minid = u ;
    }
    for( int i = tree.head[u] ; ~i ; i = tree.edge[i].nex ) {
        int ch = tree.edge[i].to ;
        if( ch == f || vis[ch] ) continue ;  

        dfs2( ch , u ) ;
    }
}  

// 返回树的重心
int getRoot( int u ) {
    // 第一遍dfs求出全部点的子树大小。和以孩子为跟的子树个数的最大值
    dfs1( u , 0 ) ;
    Sum = sum[u] ;
    Min = 0x3f3f3f3f ;
    Minid = -1 ;
    // 第二次dfs求重心
    /* 事实上假设整棵树求重心的话,我们一边dfs即可。由于这个是部分树,我们要dfs来确定哪些结点在当前树中 */
    dfs2( u , 0 ) ;
    return Minid ;
}  

int tot ;
__int64 Val[MAXN] ;

void getVal( int u , int fa , __int64 vv ) {
    Val[tot++] = add( vv , val[u] ) ;
    for( int i = tree.head[u] ; ~i ; i = tree.edge[i].nex ) {
        int to = tree.edge[i].to ;
        if( vis[to] == true )
            continue ;
        if( to == fa ) continue ;
        getVal( to , u , add( vv , val[u] ) ) ;
    }
}

map<__int64,int> mp ;

__int64 cal( int root , __int64 val , __int64 root_val ) {
    tot = 0 ;
    getVal( root , 0 , val ) ;
    //sort( Val , Val + tot ) ;
    mp.clear() ;
    __int64 ans = 0 ;
    for( int i = 0 ; i < tot ; i ++ ) {
        ans += mp[ add( root_val , fan(Val[i]) ) ] ;
        mp[Val[i]] ++ ;
    }
    return ans ;
}

void solve( int u ) {
    // 得到当前子树的重心
    int root = getRoot( u ) ;
    // 计算以root为根的结果
    ans += cal( root , 0 , val[root] ) ;
    if( val[root] == 0 ) ans ++ ;
    vis[root] = true ;
    for( int i = tree.head[root] ; ~i ; i = tree.edge[i].nex ) {
        int ch = tree.edge[i].to ;
        if( vis[ch] ) continue ;
        ans -= cal( ch , val[root] , val[root] ) ;
        solve( ch ) ;
    }
}

int main(){
    while( scanf( "%d" , &n ) != EOF )  {
        scanf( "%d" , &k ) ;
        for( int i = 0 ; i < k ; i ++ ) {
            scanf( "%d" , &prime[i] ) ;
        }
        tree.init() ;
        memset( vis , false , sizeof(vis) );
        memset( val , 0 , sizeof(val) ) ;
        for( int i = 1 ; i <= n ; i ++ ) {
            __int64 tmp ;
            scanf( "%I64d" , &tmp ) ;
            for( int j = 0 ; j < k ; j ++ ) {
                int cnt = 0 ;
                while( tmp % prime[j] == 0 ) {
                    cnt ++ ;
                    tmp /= prime[j] ;
                }
                cnt %= 3 ;
                set( val[i] , j , cnt ) ;
            }
        }
        for( int i = 0 ; i < n - 1 ; i ++ ) {
            int u , v ;
            scanf( "%d%d" , &u , &v ) ;
            tree.add( u , v ) ;
            tree.add( v , u ) ;
        }
        ans = 0 ;
        solve( 1 ) ;
        printf( "%I64d\n" , ans ) ;
    }
    return 0 ;
}
时间: 2024-08-11 09:37:27

HDU 4670 Cube number on a tree ( 树的点分治 )的相关文章

hdu 4670 Cube number on a tree(点分治)

Cube number on a tree Time Limit: 20000/10000 MS (Java/Others)    Memory Limit: 65535/65535 K (Java/Others)Total Submission(s): 1628    Accepted Submission(s): 382 Problem Description The country Tom living in is famous for traveling. Every year, man

hdu 4812 D Tree(树的点分治)

D Tree Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 102400/102400 K (Java/Others) Total Submission(s): 1687    Accepted Submission(s): 263 Problem Description There is a skyscraping tree standing on the playground of Nanjing University of

hdu 1055 &amp; poj 2054 Color a Tree 树&amp;贪心 找最大费用点和父节点合并

Color a Tree Time Limit: 1000MS Memory Limit: 30000K Total Submissions: 7144 Accepted: 2458 Description Bob is very interested in the data structure of a tree. A tree is a directed graph in which a special node is singled out, called the "root"

HDU 2665 Kth number 可持久化线段树

题意:给n个数和m个询问,询问l,r,k是从l~r中的第k小 思路:可持久化线段树的模板题 说下自己对可持久化线段树的理解吧 可持久化线段树的是可以保存历史版本的线段树,就是插进去第i个数的线段树的状态,这样我们可以通过state[r]-state[l-1]来得到state[l~r] 朴素做法就是维护n颗线段树,但是这样一般都会MLE 可持久化线段树利用了每次插入数只修改了线段树上一条链的特性来每次插入一个数只新建一条链来维护历史版本,空间复杂度O(n*logn+n*logn) 原树+新建的链

hdu4670 Cube number on a tree 点分治

这次写不容斥的版本,WA了好几次,又改成容斥的,还是没过,一怒之下把所有的int改成longlong就过了... #include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<map> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(

【点分治】hdu4670 Cube number on a tree

求树上点权积为立方数的路径数. 显然,分解质因数后,若所有的质因子出现的次数都%3==0,则该数是立方数. 于是在模意义下暴力统计即可. 当然,为了不MLE/TLE,我们不能存一个30长度的数组,而要压成一个long long. 存储状态用map即可,貌似哈希表可以随便卡掉……? 手动开栈……当然这样有可能MLE,所以还得改一些BFS…… <法一>map: #pragma comment(linker, "/STACK:1024000000,1024000000") #in

POJ 1741 Tree(树的点分治,入门题)

Tree Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 21357   Accepted: 7006 Description Give a tree with n vertices,each edge has a length(positive integer less than 1001).Define dist(u,v)=The min distance between node u and v.Give an in

hdu 5664 Lady CA and the graph(树的点分治+容斥)

题意: 给你一个有n个点的树,给定根,叫你找第k大的特殊链 .特殊的链的定义:u,v之间的路径,经过题给的根节点. 题解:(来自BC官方题解) 对于求第k大的问题,我们可以通过在外层套一个二分,将其转化为求不小于mid的有多少个的问题. 接下来我们讨论如何求树上有多少条折链的长度不小于k. 我们考虑常规的点分治(对于重心,求出其到其他点的距离,排序+单调队列),时间复杂度为O(nlog^2n),但是这只能求出普通链的数量. 我们考虑将不属于折链的链容斥掉.也即,我们需要求出有多少条长度不小于mi

【POJ 1741】 Tree (树的点分治)

Tree Description Give a tree with n vertices,each edge has a length(positive integer less than 1001). Define dist(u,v)=The min distance between node u and v. Give an integer k,for every pair (u,v) of vertices is called valid if and only if dist(u,v)