点分治真是一个好东西。可惜我不会
void calc(int u){ dfs1(u,0); ans[u]+=sum; for(int i=head[u];i;i=e[i].nxt){ int v=e[i].to; if(vis[v])continue; cnt[a[u]]++; sum-=size[v];color[a[u]]-=size[v]; change(v,u,-1); cnt[a[u]]--; tot=size[u]-size[v]; dfs2(v,u); cnt[a[u]]++; sum+=size[v];color[a[u]]+=size[v]; change(v,u,1); cnt[a[u]]--; } clear(u,0);}
首先dfs1是统计贡献的,用sum记录贡献和,color[i]记录第i种颜色的贡献。
然后根的答案就可以累加了。 那么如可判断一个颜色第一次出现?可以记录一个cnt[i]记录第i种颜色在到根的路径上出现多少次。当cnt[i]等于1的时候统计贡献。 然后cnt[a[u]]++; sum-=size[v];color[a[u]]-=size[v]; change(v,u,-1); cnt[a[u]]--;
用来消除子树贡献。dfs2统计其它子树对这颗子树的贡献。
void dfs2(int u,int f){ cnt[a[u]]++; if(cnt[a[u]]==1){ sum-=color[a[u]]; num++; } ans[u]+=sum+num*tot; for(int i=head[u];i;i=e[i].nxt){ int v=e[i].to; if(v==f||vis[v])continue; dfs2(v,u); } if(cnt[a[u]]==1){ sum+=color[a[u]]; num--; } cnt[a[u]]--;}
如果这颗子树中出现一个颜色,并且它是第一次出现,那么减去所有子树的color[a[u]],加上其它子树的节点总数,因为每一条到其它子树的路径都会产生贡献,这也是我们一开始不考虑贡献对其他子树影响的原因,因为遍历子树的时候会把这些重复的贡献减去。
更具体还是看代码。// luogu-judger-enable-o2#include#include #include #include #include using namespace std;#define int long longconst int N=101000;int Cnt,head[N];int g[N],size[N],cnt[N],a[N],sum,color[N],tot,num,root,all,vis[N],ans[N],n;struct edge{ int to,nxt;}e[N*2];void add_edge(int u,int v){ Cnt++; e[Cnt].nxt=head[u]; e[Cnt].to=v; head[u]=Cnt;}int read(){ int sum=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();} return sum*f;}void getroot(int u,int f){ g[u]=0;size[u]=1; for(int i=head[u];i;i=e[i].nxt){ int v=e[i].to; if(v==f||vis[v])continue; getroot(v,u); size[u]+=size[v]; g[u]=max(g[u],size[v]); } g[u]=max(g[u],all-size[u]); if(g[u]