【BZOJ2152】聪明的可可 树的点分治

题目大意:给定一棵n<=20000的带边权树,求dis(x,y)%3==0的有序点对(x,y)

第一次接触神奇的点分治算法,其主要思想就是对于一棵树,找到他的重心,DP计算所有经过重心点的路径中mod3==0的路径条数,然后将整棵树从重心点处断开,分为若干棵子树,化归解决原问题。其中断树的操作可以通过给边打上bool标记来实现。

/************************************************************** 
    Problem: 2152 
    User: RicardoWang 
    Language: C++ 
    Result: Accepted 
    Time:332 ms 
    Memory:3224 kb 
****************************************************************/
  
#include<cstdlib> 
#include<cstdio> 
#include<iostream> 
#include<cstring> 
#include<cmath> 
#include<algorithm> 
#include<queue> 
#include<vector> 
using namespace std; 
#define maxn 20005 
struct edge 
{ 
    int to,d,next; bool ban; 
}e[maxn*2]; 
int n,edge_ct,head[maxn],sz[maxn]; 
void add(int x,int y,int de) 
{ 
    e[++edge_ct]=(edge){y,de,head[x],false}; head[x]=edge_ct; 
    return ; 
} 
void _read(int &x) 
{ 
    char ch=getchar(); x=0;while(ch<'0'||ch>'9')ch=getchar(); while(ch>='0' && ch<='9'){x=x*10+ch-'0'; ch=getchar();}return ; 
} 
void Init() 
{ 
    _read(n);edge_ct=0; 
    int x,y,z; 
    for(int i=1;i<n;i++) 
    { 
        _read(x);_read(y); _read(z);  
        add(x,y,z%3); add(y,x,z%3); 
    } 
    return ; 
} 
void Get_gc(int now,int fa,int size,int &gc) 
{ 
    bool flag=1;int j; 
    sz[now]=1; 
    for(int id=head[now];id;id=e[id].next) 
    { 
        if(e[id].ban || e[id].to==fa)continue; 
        j=e[id].to;Get_gc(j,now,size,gc); 
        if(sz[j]>(size>>1))flag=false; 
        sz[now]+=sz[j]; 
    } 
    if(size-sz[now]>(size>>1))flag=false;  
    if(flag)gc=now; 
    return ; 
} 
int sum=0; 
int cnt[3],_cnt[3]; 
void DFS(int now,int fa,int dis) 
{ 
    _cnt[dis]++; sz[now]=1; 
    for(int id=head[now];id;id=e[id].next) 
    { 
        if(e[id].ban || e[id].to == fa)continue; 
        DFS(e[id].to,now,(dis+e[id].d)%3); 
        sz[now]+=sz[e[id].to]; 
    } 
    return ; 
} 
void Calc(int now) 
{ 
    cnt[0]=1; cnt[1]=0; cnt[2]=0; 
    for(int id=head[now];id;id=e[id].next) 
    { 
        if(e[id].ban)continue; 
        _cnt[0]=0; _cnt[1]=0; _cnt[2]=0; 
        DFS(e[id].to,now,e[id].d); 
        sum+=cnt[0]*_cnt[0]+cnt[1]*_cnt[2]+cnt[2]*_cnt[1]; 
        cnt[0]+=_cnt[0]; cnt[1]+=_cnt[1]; cnt[2]+=_cnt[2]; 
    } 
    return ; 
} 
void Tree_Devide(int now,int size) 
{ 
    int gc; 
    Get_gc(now,0,size,gc); 
    Calc(gc); 
    for(int id=head[gc];id;id=e[id].next)if(!e[id].ban) 
    { 
        e[id].ban=e[id+(id%2==0 ? -1:1)].ban=true; 
        Tree_Devide(e[id].to,sz[e[id].to]); 
    } 
    return ; 
} 
int gcd(int x,int y) 
{ 
    int t; 
    while(y){t=x%y; x=y; y=t;} 
    return x; 
} 
void work() 
{ 
    Tree_Devide(1,n); 
    sum=sum*2+n; 
    int t=gcd(sum,n*n); 
    printf("%d/%d\n",sum/t,n*n/t); 
    return; 
} 
int main() 
{ 
    //freopen("in.txt","r",stdin); 
    Init(); 
    work(); 
    return 0; 
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值