P9194 [USACO23OPEN] Triples of Cows P 题解
Link
由于每次删点之后新增的边数太多,所以考虑拆边。用一个白点表示一条边,并将原来树中的点称为黑点,编号不变。对于第
删除点
因为依次删除
用
下面来考虑如何统计答案。对白点
对于一个符合要求的
- 如果
x=y :固定x ,在x 的邻点中任选3 个,则对答案的贡献为\sum_{x} (s_x+1)s_x(s_x-1) 求和的条件是
x 是白点。 - 如果
x\not=y ,且x,y 都是b 的子节点:固定b ,先任取b 的两个子结点x,y ,此时贡献s_xs_y 。则总的贡献为\sum_{b} (\sum_{x} s_x) ^2 - \sum_{x} s_x^2 第一个求和的条件是
b 是黑点,后两个求和的条件是x 是b 的儿子。 注意到后一项拆出来就是对所有白点x 求s_x^2 的和,那就拆出来吧。 - 如果
x\not=y ,且x,y 一个是b 的子结点,另一个是b 的父亲结点:不妨x 是b 的父亲结点,固定x ,b 是x 的一个子结点,y 又是b 的一个子结点,则对答案的贡献为\sum_{x} 2\times s_x \times \sum_{b} \sum_{y} s_y 三个求和的条件分别为
x 是白点,b 是x 的子结点,y 是b 的结点。
列出式子后,我们发现需要维护以下数据:
- 白点
u 的儿子数目s_u - 黑点
u 的儿子的s 值之和,也可以存到数组s 里 - 白点
u 的儿子的s 值之和t_u
答案是
删除点
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=4e5+5;
int n,fa[N],p[N];ll s[N],t[N],ans;
int head[N],ver[N<<1],nxt[N<<1],tot;
void add(int u,int v){ver[++tot]=v;nxt[tot]=head[u];head[u]=tot;}
void dfs(int u){
for(int i=head[u],v;i;i=nxt[i])
if((v=ver[i])!=fa[u]){
fa[v]=u;dfs(v);
if(u<=n)s[u]+=s[v];
else ++s[u],t[u]+=s[v];
}
}
int find(int x){return (x==p[x]?x:(p[x]=find(p[x])));}
ll f(ll x){return x*x*x-x*x-x;}
int main(){
scanf("%d",&n);
for(int i=1,u,v;i<n;i++){
scanf("%d%d",&u,&v);
add(u,n+i);add(v,n+i);add(n+i,u);add(n+i,v);
}
dfs(n);
for(int i=1;i<2*n;i++)p[i]=i;
for(int i=1;i<=n;i++)ans+=s[i]*s[i];
for(int i=n+1;i<2*n;i++)ans+=f(s[i])+2*s[i]*t[i];
for(int u=1;u<=n;u++){
printf("%lld\n",ans);
int g=find(fa[u]),w=fa[g];ll del=-1;
ans-=f(s[g])+2*s[g]*t[g]+s[w]*s[w];s[w]-=s[g];--s[g];
t[g]-=s[u];ans-=s[u]*s[u];s[u]=0;
for(int i=head[u],v;i;i=nxt[i])
if((v=ver[i])!=fa[u]){
p[v]=g;s[g]+=s[v];t[g]+=t[v];del+=s[v];
ans-=f(s[v])+2*s[v]*t[v];s[v]=t[v]=0;
}
s[w]+=s[g];ans+=f(s[g])+2*s[g]*t[g]+s[w]*s[w];
t[w=find(fa[fa[g]])]+=del;ans+=2*s[w]*del;
}
return 0;
}
Upd:修改了格式。