【训练题38:点分治 + 状压】Garden of Eden | HDU5977

本文介绍了如何使用点分治算法解决一个图论问题:给定一棵树,每个节点有特定颜色,求包含所有颜色的路径数量。通过点分治和状态压缩,实现了O(nlogn×2^10)的时间复杂度解决方案,详细阐述了算法思路、代码实现及复杂度分析。
摘要由CSDN通过智能技术生成

前置

题意

  • 给定 n n n 个节点的树,每个节点有一个整数权值 a i ∈ [ 1 , k ] a_i\in[1,k] ai[1,k]
    问你有多少条路径,满足路径上包含了所有颜色的节点?

范围

  • 1 ≤ n ≤ 4 × 1 0 5 1\le n\le 4\times10^5 1n4×105
    1 ≤ k ≤ 10 1\le k\le 10 1k10

思路

  • 统计路径条数,想到点分治。问你包含所有颜色,想到状压。于是思路直接就出来了。
    算了一下,时间复杂度大概为 O ( n log ⁡ n × 2 10 ) O(n\log n\times 2^{10}) O(nlogn×210),限时 5000 M s 5000Ms 5000Ms 貌似可以直接冲。
  • 某个树找到重心后,我们按一个一个子树去遍历。
    其中每个节点的 d e p [ i ] dep[i] dep[i] 表示 该节点到根节点的路径上,颜色的和。
    那么统计答案,我们就是要去找到之前的桶 b i g [ S ] big[S] big[S],满足 S ∣ T = ( 1 < < k ) − 1 S|T=(1<<k)-1 ST=(1<<k)1
    答案就是这样去累加 ∑ b i g [ S   l e g a l ] \sum big[S_{\ legal}] big[S legal]
    在这里插入图片描述
  • 我们这里只需要一个小特判,就不需要去容斥做了。
    对于上述的计算,我们算出来的点对是子树间的两两点对。
    然而 ( u , v ) (u,v) (u,v) ( v , u ) (v,u) (v,u) 应该算两种,所以我们这些对之间都要乘以2。
    我们唯一没有去算的就是单拿根节点的这种方案,明显只有 k = 1 k=1 k=1 的时候,需要多累加 n n n 的方案

代码

  • 时间复杂度: O ( n log ⁡ n × 2 10 ) O(n\log n\times 2^{10}) O(nlogn×210)
    T i m e ( M s ) : 639 / 5000 Time(Ms):639/5000 Time(Ms):639/5000
/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
const int MAX = 5e4 + 50;
int n,k,sum,root;
ll ans;
vector<int>V[MAX],SS[MAX];
int sz[MAX],ff[MAX];
bool vis[MAX];
void find_rt(int x,int f){			/// 点分治求重心
    sz[x] = 1;ff[x] = 0;
    for(auto it : V[x]){
        if(it == f || vis[it])continue;
        find_rt(it,x);
        sz[x] += sz[it];
        ff[x] = max(ff[x],sz[it]);
    }
    ff[x] = max(ff[x],sum - sz[x]);
    if(ff[x] < ff[root])root = x;
}
int sml[MAX],big[1300],tmp[MAX],col[MAX],dep[MAX];
void get_col(int x,int f){			/// 求颜色 dep[i]
    sml[++sml[0]] = dep[x];
    for(auto it : V[x]){
        if(it == f || vis[it])continue;
        dep[it] = dep[x] | (1<<col[it]);
        get_col(it,x);
    }
}
void cal(int x){
    big[0] = 1;
    tmp[0] = 0;
    for(auto it : V[x]){
        if(vis[it])continue;
        sml[0] = 0;
        dep[it] = (1<<col[it]) | (1<<col[x]);		/// 从根节点x出发
        get_col(it,x);
        for(int i = 1;i <= sml[0];++i){
            for(auto j : SS[sml[i]]){
                ans += big[j];						/// 只算合法的答案
            }
        }
        for(int i = 1;i <= sml[0];++i){
            tmp[++tmp[0]] = sml[i];
            big[sml[i]]++;
        }
    }
    for(int i = 1;i <= tmp[0];++i)
        big[tmp[i]]--;
}
void pdc(int x){
    vis[x] = 1;
    cal(x);		/// 只用算从根除法的贡献即可
    for(auto it : V[x]){
        if(vis[it])continue;
        sum = sz[it];
        root = 0;
        find_rt(it,it);
        pdc(root);
    }
}

int main(){

    while(~scanf("%d%d",&n,&k)){
        for(int i = 0;i < (1<<k);++i)SS[i].clear();

        for(int i = 0;i < (1<<k);++i){
            for(int j = 0;j < (1<<k);++j){
                if((i | j) == (1<<k)-1){
                    SS[i].push_back(j);		/// 事先枚举合法的集合
                }
            }
        }
        ans = 0;
        for(int i = 1;i <= n;++i){
            scanf("%d",&col[i]);col[i]--;
            V[i].clear();
            vis[i] = false;
        }
        for(int i = 1;i < n;++i){
            int ta,tb;scanf("%d%d",&ta,&tb);
            V[ta].push_back(tb);
            V[tb].push_back(ta);
        }
        sum = n;
        root= 0;ff[0] = n;
        find_rt(1,1);
        pdc(root);
        ans = ans * 2;					/// 点对答案乘以2
        if(k == 1)ans = ans + n;		/// 简答特判即可回避容斥
        printf("%lld\n",ans);
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值