51Nod1868 彩色树 虚树
原文链接https://www.cnblogs.com/zhouzhendong/p/51Nod1868.html
题目传送门 - 51Nod1868
题意
给定一颗 $n$个点的树,每个点一个 $[1,n]$ 的颜色。设 $g(x,y)$ 表示 $x$ 到 $y$ 的树上路径上有几种颜色。
对于一个长度为 $n$ 的排列 $P[1\cdots n]$ ,定义 $f(P)=\sum_{i=1}^{n-1}g(P_i,P_{i+1})$ 。
现在求对于 $n!$ 个排列,他们的 $f(P)$ 之和 对 $10^9+7$ 取模后的值。
题解
首先我们考虑每一个 $g(x,y)$ 对于答案的贡献次数。
考虑捆绑法,把 $x$ 和 $y$ 看作一个整体,显然,它对答案的贡献次数为 $(n-1)!$ 。
于是答案就是
$$2\times (n-1)!\sum_{x=1}^{n}\sum_{y=x+1}^{n} g(x,y)$$
前面的 $2\times (n-1)!$ 很好办,现在主要要求后面的那个。
我们考虑对于每一个颜色分别处理。我们需要求出每一个颜色对答案的贡献。
记 $f(c,x,y)$ 表示路径 $x$~$y$ 上,如果有颜色 $c$ ,那么值为 $1$ ,否则为 $0$ 。则后面一半变成了:
$$\sum_{c=1}^{n}\sum_{x=1}^{n}\sum_{y=x+1}^{n} f(c,x,y)$$
确定一种颜色之后,后面的显然非常好求,直接一个树形dp 就差不多了。但是这样的时间复杂度是炸掉的。于是我们需要一个数据结构来优化——虚树。
建出虚树,然后我们注意一下细节,统计一下就可以了。
这里推荐一个写的比较详细的虚树学习笔记:https://www.k-xzy.xyz/archives/4476
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=200005,mod=1e9+7;
int read(){
int x=0;
char ch=getchar();
while (!isdigit(ch))
ch=getchar();
while (isdigit(ch))
x=(x<<1)+(x<<3)+ch-48,ch=getchar();
return x;
}
struct Gragh{
static const int M=N*2;
int cnt,y[M],nxt[M],fst[N];
void clear(){
cnt=0;
memset(fst,0,sizeof fst);
}
void add(int a,int b){
y[++cnt]=b,nxt[cnt]=fst[a],fst[a]=cnt;
}
}g,t;
int n,c[N],Fac[N],Time=0,now_color,ans=0;
int dfn[N],depth[N],size[N],fa[N][18],sqrsum[N];
int dirson[N],tot[N],st[N],top;
vector <int> id[N];
LL calc(int x){
return 1LL*x*(x-1)/2;
}
void dfs(int x,int pre,int d){
dfn[x]=++Time,depth[x]=d,size[x]=1,fa[x][0]=pre,sqrsum[x]=0;
for (int i=1;i<18;i++)
fa[x][i]=fa[fa[x][i-1]][i-1];
for (int i=g.fst[x];i;i=g.nxt[i])
if (g.y[i]!=pre){
int y=g.y[i];
dfs(y,x,d+1);
size[x]+=size[y];
sqrsum[x]=(calc(size[y])+sqrsum[x])%mod;
}
}
int LCA(int x,int y){
if (depth[x]<depth[y])
swap(x,y);
for (int i=17;i>=0;i--)
if (depth[x]-(1<<i)>=depth[y])
x=fa[x][i];
if (x==y)
return x;
for (int i=17;i>=0;i--)
if (fa[x][i]!=fa[y][i])
x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
bool cmp(int a,int b){
return dfn[a]<dfn[b];
}
void solve(int x){
int dx=dirson[x],sonsqr=tot[x]=0;
for (int k=t.fst[x];k;k=t.nxt[k]){
int y=t.y[k],&dy=dirson[y]=y;
for (int i=17;i>=0;i--)
if (depth[dy]-(1<<i)>depth[x])
dy=fa[dy][i];
solve(y);
tot[x]+=tot[y];
sonsqr=(calc(tot[y])+sonsqr)%mod;
}
if (c[x]==now_color){
tot[x]=size[x];
int xsqr=(calc(size[dx]-size[x])+sqrsum[x])%mod;
ans=(calc(size[dx])-xsqr+ans)%mod;
}
else {
ans=(calc(tot[x])-sonsqr+ans)%mod;
for (int i=t.fst[x];i;i=t.nxt[i]){
int y=t.y[i],v=size[dx]-tot[x]+tot[y]-size[dirson[y]];
ans=(1LL*tot[y]*v+ans)%mod;
}
}
}
int main(){
n=read();
for (int i=Fac[0]=1;i<=n;i++)
c[i]=read(),Fac[i]=1LL*Fac[i-1]*i%mod;
g.clear();
for (int i=1;i<n;i++){
int a=read(),b=read();
g.add(a,b);
g.add(b,a);
}
dfs(1,0,0);
for (int i=1;i<=n;i++)
id[i].clear();
for (int i=1;i<=n;i++)
id[c[i]].push_back(i);
t.clear();
for (int k=1;k<=n;k++){
if (id[k].size()<1)
continue;
sort(id[k].begin(),id[k].end(),cmp);
st[top=1]=1,t.fst[1]=0;
for (vector <int> :: iterator i=id[k].begin();i!=id[k].end();i++){
int x=*i;
if (x==1)
continue;
int lca=LCA(x,st[top]);
if (lca!=st[top]){
while (depth[st[top-1]]>depth[lca])
t.add(st[top-1],st[top]),top--;
if (st[top-1]!=lca)
t.fst[lca]=0,t.add(lca,st[top]),st[top]=lca;
else
t.add(lca,st[top--]);
}
t.fst[x]=0,st[++top]=x;
}
for (int i=1;i<top;i++)
t.add(st[i],st[i+1]);
now_color=k,dirson[1]=1;
solve(1);
}
printf("%d\n",2LL*(ans+mod)%mod*Fac[n-1]%mod);
return 0;
}
51Nod1868 彩色树 虚树的更多相关文章
- 仙人掌 && 圆方树 && 虚树 总结
仙人掌 && 圆方树 && 虚树 总结 Part1 仙人掌 定义 仙人掌是满足以下两个限制的图: 图完全联通. 不存在一条边处在两个环中. 其中第二个限制让仙人掌的题做 ...
- [SDOI2018]战略游戏(圆方树+虚树)
喜闻乐见的圆方树+虚树 图上不好做,先建出圆方树. 然后答案就是没被选到的且至少有两条边可以走到被选中的点的圆点的数量. 语文不好,但结论画画图即可得出. 然后套路建出虚树. 发现在虚树上DP可以得出 ...
- hihoCoder #1954 : 压缩树(虚树)
题意 有一棵 \(n\) 个节点且以 \(1\) 为根的树,把它复制成 \(m\) 个版本,有 \(q\) 次操作,每次对 \([l, r]\) 这些版本的 \(v\) 节点到根的路径收缩起来. 收缩 ...
- Codechef Sad Pairs——圆方树+虚树+树上差分
SADPAIRS 删点不连通,点双,圆方树 非割点:没有影响 割点:子树DP一下 有不同颜色,所以建立虚树 在圆方树上dfs时候 如果当前点是割点 1.统计当前颜色虚树上的不连通点对,树形DP即可 2 ...
- BZOJ5329:[SDOI2018]战略游戏(圆方树,虚树)
Description 省选临近,放飞自我的小Q无心刷题,于是怂恿小C和他一起颓废,玩起了一款战略游戏. 这款战略游戏的地图由n个城市以及m条连接这些城市的双向道路构成,并且从任意一个城市出发总能沿着 ...
- Luogu P4606 [SDOI2018] 战略游戏 圆方树 虚树
https://www.luogu.org/problemnew/show/P4606 把原来的图的点双联通分量缩点(每个双联通分量建一个点,每个割点再建一个点)(用符合逻辑的方式)建一棵树(我最开始 ...
- BZOJ.5329.[SDOI2018]战略游戏(圆方树 虚树)
题目链接 显然先建圆方树,方点权值为0圆点权值为1,两点间的答案就是路径权值和减去起点终点. 对于询问,显然可以建虚树.但是只需要计算两关键点间路径权值,所以不需要建出虚树.统计DFS序相邻的两关键点 ...
- UOJ.87.mx的仙人掌(圆方树 虚树)(未AC)
题目链接 本代码10分(感觉速度还行..). 建圆方树,预处理一些东西.对询问建虚树. 对于虚树上的圆点直接做:对于方点特判,枚举其所有儿子,如果子节点不在该方点代表的环中,跳到那个点并更新其val, ...
- 洛谷P4606 [SDOI2018]战略游戏 【圆方树 + 虚树】
题目链接 洛谷P4606 双倍经验:弱化版 题解 两点之间必经的点就是圆方树上两点之间的圆点 所以只需建出圆方树 每次询问建出虚树,统计一下虚树边上有多少圆点即可 还要讨论一下经不经过根\(1\)的情 ...
随机推荐
- orm分组,聚合查询,执行原生sql语句
from django.db.models import Avg from app01 import models annotate:(聚合查询) ret=models.Article.objects ...
- VUE 密码验证与提示
1. 概述 1.1 说明 vue项目中,为了较为明了的让用户看到所输入的密码信息的长度与复杂度是否满足要求,开发一个组件来满足此需求(当密码输入时进行密码验证操作,当密码的长度在8到24位之间,密码中 ...
- Python-WEB -VUE初识
走进Vue_渐进式 JavaScript 框架 通过对框架的了解与运用程度,来决定其在整个项目中的应用范围,最终可以独立以框架方式完成整个web前端项目 what -- 什么是Vue 可以独立完成前后 ...
- jquery中的attr与prop的区别,什么时候用attr,什么时候用prop
只要有 Boolean() 属性的,简单说就是具有true 和 false 两个属性的属性,如 checked, selected 或者 disabled 使用prop(),(其实这些都是表单类的), ...
- JAVA 语言如何进行异常处理,关键字: throws,throw,try,catch,finally分别代表什么意义? 在try块中可以抛 出异常吗?
Java通过面向对象的方法进行异常处理,把各种不同的异常进行分类, 并提供了良好的接口. 在 Java中,每个异常都是一个对象,它是 Throwable 类或其它子类的实例.当一个方法出 ...
- 对Swoole、Workerman和php自带的socket的理解
为什么php自带的socket不怎么听说,基本都是用swoole,workerman去实现? 1.PHP的socket扩展是一套socket api,仅此而已. swoole,用C实现,它的socke ...
- python并发编程之进程池,线程池,协程
需要注意一下不能无限的开进程,不能无限的开线程最常用的就是开进程池,开线程池.其中回调函数非常重要回调函数其实可以作为一种编程思想,谁好了谁就去掉 只要你用并发,就会有锁的问题,但是你不能一直去自己加 ...
- 三.NFS存储服务
01. 课程回顾 备份服务概念介绍(rsync备份服务利用相应算法,实现增量数据同步) 备份服务工作方式说明: 1. 本地数据备份同步方式(类似cp命令) 2. 远程数据备份同步方式(类似scp命令) ...
- bzoj 1222
比较简单的背包dp,设计状态f[i][j]表示到了前i个物品,第一台机器加工时间为j,第二台机器加工所用的最小时间,然后背包转移即可 本题卡空间,需要滚动数组优化 本题卡时间,稍微卡下常就行 #inc ...
- bzoj 1495
这是一道...卡了我一个月的树形dp... 我真是太弱了... 其实仔细想想,这题的核心思路并不是特别复杂,但是的确存在不小的难度 作为一个看过全网基本所有题解+标程才弄明白这题到底怎么回事的蒟蒻,我 ...