原文链接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 彩色树 虚树的更多相关文章

  1. 仙人掌 && 圆方树 && 虚树 总结

    仙人掌 && 圆方树 && 虚树 总结 Part1 仙人掌 定义 仙人掌是满足以下两个限制的图: 图完全联通. 不存在一条边处在两个环中. 其中第二个限制让仙人掌的题做 ...

  2. [SDOI2018]战略游戏(圆方树+虚树)

    喜闻乐见的圆方树+虚树 图上不好做,先建出圆方树. 然后答案就是没被选到的且至少有两条边可以走到被选中的点的圆点的数量. 语文不好,但结论画画图即可得出. 然后套路建出虚树. 发现在虚树上DP可以得出 ...

  3. hihoCoder #1954 : 压缩树(虚树)

    题意 有一棵 \(n\) 个节点且以 \(1\) 为根的树,把它复制成 \(m\) 个版本,有 \(q\) 次操作,每次对 \([l, r]\) 这些版本的 \(v\) 节点到根的路径收缩起来. 收缩 ...

  4. Codechef Sad Pairs——圆方树+虚树+树上差分

    SADPAIRS 删点不连通,点双,圆方树 非割点:没有影响 割点:子树DP一下 有不同颜色,所以建立虚树 在圆方树上dfs时候 如果当前点是割点 1.统计当前颜色虚树上的不连通点对,树形DP即可 2 ...

  5. BZOJ5329:[SDOI2018]战略游戏(圆方树,虚树)

    Description 省选临近,放飞自我的小Q无心刷题,于是怂恿小C和他一起颓废,玩起了一款战略游戏. 这款战略游戏的地图由n个城市以及m条连接这些城市的双向道路构成,并且从任意一个城市出发总能沿着 ...

  6. Luogu P4606 [SDOI2018] 战略游戏 圆方树 虚树

    https://www.luogu.org/problemnew/show/P4606 把原来的图的点双联通分量缩点(每个双联通分量建一个点,每个割点再建一个点)(用符合逻辑的方式)建一棵树(我最开始 ...

  7. BZOJ.5329.[SDOI2018]战略游戏(圆方树 虚树)

    题目链接 显然先建圆方树,方点权值为0圆点权值为1,两点间的答案就是路径权值和减去起点终点. 对于询问,显然可以建虚树.但是只需要计算两关键点间路径权值,所以不需要建出虚树.统计DFS序相邻的两关键点 ...

  8. UOJ.87.mx的仙人掌(圆方树 虚树)(未AC)

    题目链接 本代码10分(感觉速度还行..). 建圆方树,预处理一些东西.对询问建虚树. 对于虚树上的圆点直接做:对于方点特判,枚举其所有儿子,如果子节点不在该方点代表的环中,跳到那个点并更新其val, ...

  9. 洛谷P4606 [SDOI2018]战略游戏 【圆方树 + 虚树】

    题目链接 洛谷P4606 双倍经验:弱化版 题解 两点之间必经的点就是圆方树上两点之间的圆点 所以只需建出圆方树 每次询问建出虚树,统计一下虚树边上有多少圆点即可 还要讨论一下经不经过根\(1\)的情 ...

随机推荐

  1. 前端 -----jQuery的事件绑定和解绑

    11-jQuery的事件绑定和解绑   1.绑定事件 语法: bind(type,data,fn) 描述:为每一个匹配元素的特定事件(像click)绑定一个事件处理器函数. 参数解释: type (S ...

  2. Confluence 6 数据库整合的方法 2:针对有大量附件的运行实例

    设置准备 这个方法仅仅针对附件存储在文件系统中.如果你存储附件在数据库中,请参考 Attachment Storage Configuration 文档中的内容来找到如何在 2 种不同的文件存储方式之 ...

  3. VMware安装windows10系统

  4. day07 元组类型 字典类型 集合

    元组:元组就是一个不可变的列表 1.用途:当我们需要记录多个同种属性的值,并且只有读的需求,没有改的需求,应该用元组. 2.定义方式:在()内用逗号分隔开多个任意类型的元素 t=(‘egon’)#注意 ...

  5. sql查询条件为空的另类写法o( ̄▽ ̄)d

    简单描述:今天看老大提交的代码,发现了一个有意思的事情,一条sql中判断条件是空,老大的写法,让我眼前一亮.直接上代码 代码: <select id="getxxxs" re ...

  6. 【docker】私有仓库搭建

    主要参考:http://blog.csdn.net/gqtcgq/article/details/51163558 假设我们在1.1.1.1:5000上搭建私人仓库,并在2.2.2.2上访问这个私人仓 ...

  7. 【python】统一转换日期格式dateutil.parser.parse

    背景: 我有很多很多的日志数据,每个日志里面都有日期字符串,我需要将其转换为datetime格式. 问题是,这些日志里的字符串格式五花八门,有2017-05-25T05:27:30.313292255 ...

  8. (转)webpack和webpack-simple区别(如何引入css文件)

    博主最近研究vue+webpack的时候想引入css文件死活引入不出来,在webpack-simple那里却能引得出来,十分的纳闷,然后细心的调试了一下,原来这webpack和webpack-simp ...

  9. jmeter 中如何一次运行多条sql语句

    在jmeter测试mysql中如何一次运行多条sql语句 allowMultiQueries=true 注意:太低版本的mysql和jdbc不支持,最好用最新版的

  10. 20165206 实验一 Java开发环境的熟悉

    20165206 实验一 Java开发环境的熟悉 一.实验内容及步骤 实验一 Java开发环境的熟悉-1 建立有自己学号的实验目录. 通过vim Hello.java编辑代码. 编译.运行Hello. ...