Wannafly挑战赛19-C-多彩的树(状压+容斥)

链接:https://www.nowcoder.com/acm/contest/131/C
来源:牛客网

多彩的树

时间限制:C/C++ 5秒,其他语言10秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld

题目描述

有一棵树包含 N 个节点,节点编号从 1 到 N。节点总共有 K 种颜色,颜色编号从 1 到 K。第 i 个节点的颜色为 Ai。
Fi 表示恰好包含 i 种颜色的路径数量。请计算:

输入描述:

第一行输入两个正整数 N 和 K,N 表示节点个数,K 表示颜色种类数量。
第二行输入 N 个正整数,A1, A2, A3, ... ..., AN,Ai 表示第 i 个节点的颜色。

接下来 N - 1 行,第 i 行输入两个正整数 Ui 和 Vi,表示节点 Ui 和节点 Vi 之间存在一条无向边,数据保证这 N-1 条边连通了 N 个节点。

1 ≤ N ≤ 50000.
1 ≤ K ≤ 10.
1 ≤ Ai ≤ K.

输出描述:

输出一个整数表示答案。

示例1

输入

5 3
1 2 1 2 3
4 2
1 3
2 1
2 5

输出

4600065

思路:对于一个大小为x的树,它的路径条数为x+x*(x-1)。

由于k<=10,考虑直接枚举当前所要计算的颜色的状态zt,然后直接遍历所有点,如果点u的颜色包含在这个状态里,就记jd[u]=1并且累加其所有儿子节点v的jd[v],否则记jd[u]=0。

这样,我们就可以依次查找每个点,如果u被包含在这个状态里,但是它的父节点f[u]不包含在这个状态里,那么以u为根的子树就形成了一个独立的全部包含于zt的连通块。这时候就可以累加它的路径条数。

然鹅这时候我们求的是 包含颜色 <= zt中1的个数 的路径条数。因此求完以后,我们再容斥一下,减去 包含颜色 <= zt中1的个数-1 的路径条数即可。由于不影响最终答案,因此含有相同1个数的zt我们可以分开计算,累加求和。

代码:

#include<bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f
#define rep(i,a,b) for(register int i=(a);i<=(b);i++)
#define dep(i,a,b) for(register int i=(a);i>=(b);i--)
using namespace std;
const int maxn=1e5+5;
//const double pi=acos(-1.0);
//const double eps=1e-9;
const ll mo=1e9+7;
int n,m,k;
int a[maxn],b[maxn],c[maxn];
int flag,cnt,num,cm;
int ok[maxn],co[maxn],jd[maxn];
int he[maxn],fa[maxn];
ll f[maxn],pw[maxn];
struct node{
    int v,nxt;
}e[maxn<<1];
template <typename T>
inline void read(T &X)
{
    X=0;int w=0; char ch=0;
    while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
    while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    if(w) X=-X;
}
void add(int u,int v){
    e[cnt].v=v;
    e[cnt].nxt=he[u];
    he[u]=cnt++;
}
int count(int x){
    int sum=0;
    while(x){
        x&=(x-1);
        sum++;
    }
    return sum;
}
bool cmp(int x,int y){return c[x]<c[y];}
void dfs(int u,int ff,int zt){
    fa[u]=ff;
    if(zt&(1<<(a[u]-1))) jd[u]=1;
    else jd[u]=0;
    for(int i=he[u];i;i=e[i].nxt){
        int v=e[i].v;
        if(v==ff) continue;
        dfs(v,u,zt);
        if(jd[u]) jd[u]+=jd[v];
    }
}
int main()
{
    pw[0]=1;
    rep(i,1,10) pw[i]=(pw[i-1]*131)%mo;
    int T,cas=1;
    //read(T);
    //while(T--)
    {
        read(n);read(k);
        cnt=1;
        rep(i,1,n) read(a[i]);
        rep(i,1,n-1) {
            int u,v;
            read(u);read(v);
            add(u,v);
            add(v,u);
        }
        int m=(1<<k)-1;
        rep(i,1,m) {c[i]=count(i);}
        ll ans=0;
        rep(s,1,m){
            f[s]=0;
            dfs(1,0,s);
            rep(i,1,n) if(jd[i]&&!jd[fa[i]]){
                f[s]+=jd[i]+(ll)jd[i]*(jd[i]-1)/2;
            }
        }
        rep(s,1,m){
            for(int sub=s&(s-1);sub;sub=(sub-1)&s){
                f[s]-=f[sub];
            }
            ans=(ans+f[s]*pw[c[s]]%mo)%mo;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值