前言
时间紧,就只写最关键的高维前缀和部分
介绍
其实我一开始也不知道这样一个东西,但我也做出来了,自己yy
把他想成一个dp,毕竟前缀和本身就是最短的dp.
dp有一个思想就是我们一定要充分的假设dp[i][j]是未知的,但推出这个状态的式子是已知的,尽管你觉得这样一个推出这个状态的式子本身不可求。
很简单的,就是要求
dp[(1010...)2]
d
p
[
(
1010...
)
2
]
这样一个式子的值,意思就是括号内二进制数的出现次数与改二进制数上任意1个或多个位置的0变成1的新二进制数出现的次数的和。(有点拗口)。假设该二进制数为
(1010)2
(
1010
)
2
如果限定所有二进制数的最高位为第4位,那么现在这个式子就不用求了,如果再增加一位至第五位,那么他表示的其实是
dp[(01010)2]
d
p
[
(
01010
)
2
]
的状态,还差一个
dp[(11010)2]
d
p
[
(
11010
)
2
]
的值,加上去就好了。
这个东西也出现在或的贪心里。复杂度O(nlogn);
题面
一大早过来补题面。意思就是求有多少条树上路径包含所有颜色的树上路径。每个点都有颜色.
解法
点分治不用想了。这道题唯一的难点上面已经讲了,唯一的细节就是权在点上,仔细想想加点权的顺序。具体操作看代码。
代码
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int _ =5e4+4,INF = 2e9;
struct edge{
int to,nt;
}e[_<<1];
int head[_],size[_],root;
bool vis[_];
int n,k,cnt,all,MX,K,col[_];
LL ans,tong1[1025],tong2[1025];
inline void add(register int a,register int b){
e[++cnt].to=a,e[cnt].nt=head[b],head[b]=cnt;
}
void getroot(register int now,register int fa){
int mx=0;size[now]=1;
for(register int i=head[now];i;i=e[i].nt){
if(vis[e[i].to]||e[i].to==fa)continue;
getroot(e[i].to,now);
size[now]+=size[e[i].to];
if(size[e[i].to]>mx)mx=size[e[i].to];
}
mx=max(mx,all-size[now]);
if(MX>mx)root=now,MX=mx;
return;
}
void dfs(register int now,register int fa,register int len){
for(register int i=head[now];i;i=e[i].nt){
if(e[i].to==fa)continue;
if(vis[e[i].to])continue;
tong1[len|(1<<col[e[i].to])]++,tong2[len|(1<<col[e[i].to])]++;
dfs(e[i].to,now,len|(1<<col[e[i].to]));
}
}
inline LL getdis(register int now,register int len){
tong1[(1<<col[now])|len]++,tong2[(1<<col[now])|len]++;
dfs(now,0,(1<<col[now])|len);
//cout<<now<<' '<<len<<endl;
/*for(register int i=0;i<=K;++i){
cout<<tong1[i]<<' ';
}
cout<<endl;*/
for(register int i=0;i<k;++i){
for(register int j=K;j>=0;--j){
if(!((1<<i)&j))tong2[j]+=tong2[(1<<i)|j];
}
}
LL ret=0;
for(register int i=0;i<=K;++i)ret+=tong1[i]*(tong2[(i^K)]);
//ret+=tong1[k]*(tong2[0]-1);
for(register int i=0;i<=K;++i)tong1[i]=tong2[i]=0;
//cout<<ret<<endl;
return ret;
}
void divide(register int now){
vis[now]=1;
ans+=getdis(now,0);
for(register int i=head[now];i;i=e[i].nt){
if(vis[e[i].to])continue;
ans-=getdis(e[i].to,(1<<col[now]));
all=size[e[i].to];MX=INF;
getroot(e[i].to,now);
divide(root);
}
}
int main(){
//freopen("data.in","r",stdin);
while(~scanf("%d%d",&n,&k)){
memset(head,0,sizeof(head));
K=(1<<k)-1;
memset(vis,0,sizeof(vis));cnt=0;ans=0;
for(register int i=1;i<=n;++i)scanf("%d",&col[i]),col[i]--;
for(register int i=1;i<n;++i){
register int a,b;
scanf("%d%d",&a,&b);
add(a,b);add(b,a);
}
all=n;MX=INF;
getroot(1,0);
divide(root);
printf("%lld\n",ans);
}
return 0;
}
总结
感谢YCB推荐这道稍微没有那么板子的点分治,刚刚适合我这种蒟蒻。。