传送门
// 一棵树, 上面都有一个点权, 问有多少条路径满足进过的点的类型刚好有k种,
// 首先这个肯定是点分治, 但是难点和POJ那题的不同处就是这个是状态, 那么就不能像POJ那题进行排序选点了, 所以我们应该要换一种比较高效的方法. 首先是k只有10, 那么我们进行状压, 那么我们要求的就是路径上这条路与( | )起来等于( 1<< k) - 1, 那么我们肯定不能n方的找出两条路径来判断是否满足这个条件, 而应该是利用高位前缀和的思想.
因为我们要求的是某一条路径和已知的路径或起来的值, 即是已知路径中的二进制表示中只要为0的地方是1就行了. 所以就用到了高位前缀和, 比如dp[100] = dp[100] + dp[101] + dp[110] + dp[111]; 这样我们就可以直接算出答案从而优化复杂度. 细节请看代码.
AC Code
const int maxn = 5e4+5;
int n, cnt, head[maxn], k, vis[maxn], root, maxx, dis[maxn];
int num, tot, siz[maxn], mv[maxn];
int a[maxn];
struct node {
int to, w, next;
} e[maxn<<1];
void add(int u, int v, int w)
{
e[cnt] = (node){v,w,head[u]};
head[u] = cnt++;
}
void getroot(int u, int fa)
{
siz[u] = 1, mv[u] = 0;
for (int i = head[u]; ~i; i = e[i].next) {
int to = e[i].to;
if (to == fa || vis[to]) continue;
getroot(to, u);
siz[u] += siz[to];
mv[u] = max(mv[u], siz[to]);
}
mv[u] = max(mv[u], tot - siz[u]);
if (mv[u] < mv[root]) root = u;
}
int dp[1030]; //这个不能开太大,否则会T.
void getdis(int u,int fa,int dep)
{
dp[dep]++;
dis[++num] = dep;
for (int i = head[u]; ~i; i = e[i].next) {
int to = e[i].to;
if (to == fa || vis[to]) continue;
getdis(to, u, dep | (1 << a[to]));
}
}
ll ans;
ll cal(int u,int f)
{
num = 0; Fill(dp,0);
getdis(u,-1,f | (1 << a[u]));
for(int i=0;ifor(int j = 0 ; j < (1<if(!((1<1 << i)];
}
}
ll res = 0;
for(int i=1;i<=num;i++){
int tmp = dis[i];
tmp ^= (1<1;
res += dp[tmp];
}
return res;
}
void work(int u)
{
vis[u] = 1;
ans += cal(u, 1 << a[u]);
for (int i = head[u]; ~i; i = e[i].next) {
int to = e[i].to;
if (vis[to]) continue;
ll tmp = cal(to, 1 << a[u]);
ans -= tmp;
mv[root=0] = tot = siz[to];
getroot(to, -1);
work(root);
}
}
void solve()
{
while(~scanf("%d%d",&n,&k)){
cnt = 0 ; Fill(head,-1); Fill(vis,0);
for(int i=1;i<=n;i++) {
scanf("%d",&a[i]);
a[i]--;
}
for (int i = 1; i < n; i++) {
int u, v, w;
scanf("%d%d",&u,&v);
add(u, v, 0); add(v, u, 0);
}
ans = 0;
mv[root=0] = tot = n;
getroot(1, -1);
work(root);
cout << ans << endl;
}
}