题目链接:https://ac.nowcoder.com/acm/problem/17061
题目大意
n个点,n-1条边的树,每个节点都有颜色,一共有k种颜色,
表示颜色有i种的路径数,求解
1<=k<=10, 1<=n<=5e4
思路
颜色数量少,可以上状压,表示选中的颜色,比如5(101)表示当前选的颜色1,3。然后按照这些颜色跑树上的连通块,当前连通块大小是cnt,那么可以形成的路径数是cnt(单点)+(两个点)
但是呢比如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;
}