多彩的树(状压dp + 数学)

题目链接:https://ac.nowcoder.com/acm/problem/17061

题目大意

n个点,n-1条边的树,每个节点都有颜色a_i,一共有k种颜色,F_i表示颜色有i种的路径数,求解\sum_{i=1}^{k} (F_i*i^{131}) mod (1e9+7)

1<=k<=10, 1<=n<=5e4

思路

颜色数量少,可以上状压,表示选中的颜色,比如5(101)表示当前选的颜色1,3。然后按照这些颜色跑树上的连通块,当前连通块大小是cnt,那么可以形成的路径数是cnt(单点)+C_{cnt}^{2}(两个点)

但是呢比如5(101)包含了4(100),1(001)这些情况,要进行容斥,最后累加即可。

ac代码

#include<bits/stdc++.h>
using namespace std;
#define io cin.tie(0);ios::sync_with_stdio(false);
#define debug(x) cout<<#x<<"="<<x<<endl
#define ll long long
#define rs p<<1|1
#define ls p<<1
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const int maxn = 5e4 + 5;

ll f[25], dp[1 << 10], cnt;
vector<int> v[maxn];
int color[maxn], vis[maxn];
void dfs(int x, int u){//跑连通块
    vis[x] = 1; cnt ++;
    for(auto it : v[x]){
        if(!vis[it] && (u >> (color[it] - 1)) & 1) //状态u包含颜色it,并且之前it没用过才能跑
            dfs(it, u);
    }
}
int popcnt(int s){ //二进制1的个数
    int ans = 0;
    while(s){
        ans += s % 2;
        s >>= 1;
    }
    return ans;
}
void solve(){
    f[0] = 1;
    int n, k;
    cin >> n >> k;
    for(int i = 1; i <= k; i ++) f[i] = f[i - 1] * 131 % mod; //预处理f数组
    for(int i = 1; i <= n; i ++) cin >> color[i];
    for(int i = 1; i < n; i ++){
        int x, y;
        cin >> x >> y;
        v[x].push_back(y);
        v[y].push_back(x);
    }
    ll ans = 0;
    for(int t = 1; t < (1 << k); t ++){
        fill(vis, vis + maxn, 0);
        for(int i = 1; i <= n; i ++){
            if(!vis[i] && (t >> (color[i] - 1)) & 1){
                cnt = 0;
                dfs(i, t);
                dp[t] = (dp[t] + cnt + cnt * (cnt - 1) / 2) % mod; //这些连通块都与状态t有关
            }
        }
    }
    for(int t = 1; t < (1 << k); t ++){
        for(int i = (t - 1) & t; i; i = (i - 1) & t){ //i是t计算的重复状态,减去
            dp[t] = (dp[t] - dp[i] + mod) % mod;
        }
        ans = (ans + dp[t] * f[popcnt(t)] % mod) % mod;
    }
    cout << ans << endl;
}

int main(){
    io;
    solve();
    return 0;
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值