首页 > 其他分享 >9.树上问题

9.树上问题

时间:2025-01-21 21:10:00浏览次数:1  
标签:cnt siz ll 问题 num ans 树上 dis

树上问题

开题顺序: \(ALFBD\)

\(A\) luogu P2515 [HAOI2010] 软件安装

\(B\) CF494D Birthday

  • 将式子拆成 \(2\sum\limits_{x \in \operatorname{Subtree}(v)}dis_{u,x}^{2}-\sum\limits_{i=1}^{n}dis_{u,i}^{2}\) 的形式。

  • \(\sum\limits_{i=1}^{n}dis_{u,i}^{2}\) 换根 \(DP\) 加完全平方公式是容易维护的。

  • 将前面的式子拆开,有 \(dis_{u,x}^{2}=(dis_{1,x}+dis_{1,u}-2dis_{1,\operatorname{LCA}(u,x)})^{2}=dis_{1,u}^{2}+dis_{1,x}^{2}+4dis_{1,\operatorname{LCA}(u,x)}^{2}+2dis_{1,u}dis_{1,x}-4dis_{1,u}dis_{{1,\operatorname{LCA}(u,x)}}-4dis_{1,x}dis_{{1,\operatorname{LCA}(u,x)}}\) 。

  • 将常量 \(u\) 提出去,需要额外处理出 \(\sum\limits_{y \in Subtree(x)}dis_{1,y},\sum\limits_{y \in Subtree(x)}dis_{1,y}^{2}\) 。

  • 剩下的部分涉及 \(\operatorname{LCA}\) 的求解,需要进行分讨。

    • 当 \(u \notin \operatorname{Subtree}(v)\) 时是容易维护的。
    • 当 \(u \in \operatorname{Subtree}(v)\) 时,发现 \(v \to u\) 这条链上的点作为 \(\operatorname{LCA}\) 时难以进行统计答案,需要将式子变形成 \(\sum\limits_{i=1}^{n}dis_{u,i}^{2}-2\sum\limits_{x \notin \operatorname{Subtree}(v)}dis_{u,x}^{2}\) ,然后完全平方公式展开即可。
    点击查看代码
    const ll p=1000000007;
    struct node
    {
    	ll nxt,to,w;
    }e[200010];
    ll head[100010],fa[100010],siz[100010],dep[100010],dis[100010],son[100010],top[100010],f[2][100010],g[2][100010],num[2][100010],n,cnt=0;
    void add(ll u,ll v,ll w)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	e[cnt].w=w;
    	head[u]=cnt;
    }
    void dfs1(ll x,ll father)
    {
    	siz[x]=1;
    	fa[x]=father;
    	dep[x]=dep[father]+1;
    	num[0][x]=dis[x];
    	num[1][x]=dis[x]*dis[x]%p;
    	for(ll i=head[x];i!=0;i=e[i].nxt)
    	{
    		if(e[i].to!=father)
    		{
    			dis[e[i].to]=(dis[x]+e[i].w)%p;
    			dfs1(e[i].to,x);
    			siz[x]+=siz[e[i].to];
    			son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x];
    			num[0][x]=(num[0][x]+num[0][e[i].to])%p;
    			num[1][x]=(num[1][x]+num[1][e[i].to])%p;
    			f[0][x]=(f[0][x]+f[0][e[i].to]+siz[e[i].to]*e[i].w%p)%p;
    			f[1][x]=(f[1][x]+f[1][e[i].to]+2*e[i].w%p*f[0][e[i].to]%p+e[i].w*e[i].w%p*siz[e[i].to]%p)%p;
    		}
    	}
    }
    void dfs2(ll x,ll id)
    {
    	top[x]=id;
    	for(ll i=head[x];i!=0;i=e[i].nxt)
    	{
    		if(e[i].to!=fa[x])
    		{
    			g[0][e[i].to]=(g[0][x]+(n-2*siz[e[i].to]+p)*e[i].w%p)%p;
    			g[1][e[i].to]=(g[1][x]+e[i].w*e[i].w%p*(n-2*siz[e[i].to]+p)%p)%p;
    			g[1][e[i].to]=(g[1][e[i].to]+2*e[i].w%p*((g[0][x]-2*f[0][e[i].to]%p-e[i].w*siz[e[i].to]%p+2*p)%p))%p;
    			dfs2(e[i].to,(e[i].to==son[x])?id:e[i].to);
    		}
    	}
    }
    ll lca(ll u,ll v)
    {
    	while(top[u]!=top[v])
    	{
    		if(dep[top[u]]>dep[top[v]])  u=fa[top[u]];
    		else  v=fa[top[v]];
    	}
    	return dep[u]<dep[v]?u:v;
    }
    int main()
    {
    // #define Isaac
    #ifdef Isaac
    	freopen("in.in","r",stdin);
    	freopen("out.out","w",stdout);
    #endif
    	ll m,u,v,w,rt,ans=0,i;
    	cin>>n;
    	for(i=1;i<=n-1;i++)
    	{
    		cin>>u>>v>>w;
    		add(u,v,w);  add(v,u,w);
    	}
    	dfs1(1,0);
    	g[0][1]=f[0][1];  g[1][1]=f[1][1];
    	dfs2(1,1);
    	cin>>m;
    	for(i=1;i<=m;i++)
    	{
    		cin>>u>>v;
    		rt=lca(u,v);
    		if(rt==v)
    		{   
    			w=(dis[u]-dis[v]+p)%p;
    			ans=(g[1][v]-f[1][v]+2*w%p*(g[0][v]-f[0][v]+p)%p+w*w%p*(n-siz[v])%p+p)%p;
    			ans=(g[1][u]-2*ans%p+p)%p;
    		}
    		else
    		{
    			ans=(dis[u]*dis[u]%p*siz[v]%p+num[1][v]+2*dis[u]%p*num[0][v]%p)%p;
    			ans=(ans+4*dis[rt]%p*dis[rt]%p*siz[v]%p)%p;
    			ans=(ans-4*dis[u]%p*dis[rt]%p*siz[v]%p+p)%p;
    			ans=(ans-4*dis[rt]%p*num[0][v]%p+p)%p;
    			ans=(ans*2%p-g[1][u]+p)%p;
    		}
    		cout<<ans<<endl;
    	}
    	return 0;
    }
    
    

\(C\) luogu P4757 [CERC2014] Parades

\(D\) [ARC101E] Ribbons on Tree

  • 考虑正难则反,统计不合法的数量。具体地,设 \(F(S)\) 表示断掉 \(S\) 中的边后的方案数,则有 \(\sum\limits_{S \subseteq E}(-1)^{|S|}F(S)\) 即为所求。

  • 断掉这些边后原树分成了若干个大小为偶数的连通块,每个连通块内部两两配对,设大小为 \(2siz\) 则其内部的方案数为 \(\prod\limits_{i=1}^{siz}(2siz-1)\) ,记为 \(h_{siz}\) 。

  • 不妨统计以 \(x\) 为根的子树内 \(x\) 所在连通块大小为 \(i\) 时子树内部的贡献 \(f_{x,i}\)(包括容斥系数在内)。

  • 转移时考虑 \((x,y)\) 这条边是否断掉,分别有 \(\begin{cases} f_{x,i}' \gets -f_{x,i}f_{y,j}h_{j} \\ f'_{x,i+j} \gets f_{x,i}f_{y,j} \end{cases}\) ,需要辅助数组进行转移。

  • 最终,有 \(\sum\limits_{i=1}^{n}[i \bmod 2=0] \times f_{1,i}h_{i}\) 即为所求。

    点击查看代码
    const int p=1000000007;
    struct node
    {
    	int nxt,to;
    }e[10010];
    int head[5010],siz[5010],h[5010],f[5010][5010],g[5010],cnt=0;
    void add(int u,int v)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	head[u]=cnt;
    }
    void dfs(int x,int fa)
    {
    	siz[x]=1;
    	f[x][1]=1;
    	for(int i=head[x];i!=0;i=e[i].nxt)
    	{
    		if(e[i].to!=fa)
    		{
    			dfs(e[i].to,x);
    			for(int j=1;j<=siz[x]+siz[e[i].to];j++)  g[j]=0;
    			for(int j=1;j<=siz[x];j++)
    			{
    				for(int k=1;k<=siz[e[i].to];k++)
    				{
    					g[j+k]=(g[j+k]+1ll*f[x][j]*f[e[i].to][k]%p)%p;
    					g[j]=(g[j]-1ll*f[x][j]*f[e[i].to][k]%p*h[k]%p+p)%p;
    				}
    			}
    			siz[x]+=siz[e[i].to];
    			for(int j=1;j<=siz[x];j++)  f[x][j]=g[j];
    		}
    	}
    }
    int main()
    {
    // #define Isaac
    #ifdef Isaac
    	freopen("in.in","r",stdin);
    	freopen("out.out","w",stdout);
    #endif
    	int n,u,v,ans=0,i;
    	cin>>n;
    	h[0]=1;
    	for(i=2;i<=n;i++)
    	{
    		cin>>u>>v;
    		add(u,v);  add(v,u);
    		if(i%2==0)  h[i]=1ll*h[i-2]*(i-1)%p;
    	}
    	dfs(1,0);
    	for(i=2;i<=n;i+=2)  ans=(ans+1ll*f[1][i]*h[i]%p)%p;
    	cout<<ans<<endl;
    	return 0;
    }
    

\(E\) luogu P3748 [六省联考 2017] 摧毁“树状图”

\(F\) luogu P2305 [NOI2014] 购票

\(G\) luogu P10805 [CEOI2024] 加油站

\(H\) luogu P4220 [WC2018] 通道

\(I\) LibreOJ 3661. 「2021 集训队互测」蜘蛛爬树

\(J\) luogu P5642 人造情感(emotion)

\(K\) luogu P6830 [IOI2020] 连接擎天树

\(L\) LibreOJ 6669.Nauuo and Binary Tree

\(M\) LibreOJ 2398. 「JOISC 2017 Day 3」自然公园

\(N\) CF1919G Tree LGM

\(O\) CF1919H Tree Diameter

标签:cnt,siz,ll,问题,num,ans,树上,dis
From: https://www.cnblogs.com/The-Shadow-Dragon/p/18684376

相关文章

  • 点分治维护树上修改与查询
    点分治维护树上修改与查询具体方法就是将操作(修改与查询)离线,并打上时间戳,将其挂在点上,这样就可以考虑一个点到另一个点的贡献是否可以在其询问之前到达。对于所有的点分治都要效:避免算到同一个子树中,可以先整体计算后,在分别进入每个子树中,这样就可以不使用动态开点线段树了......
  • 解决 WebSocket 连接断开问题:前端心跳机制的实现与优化
    在开发过程中,我们经常会遇到需要实时通信的场景,而WebSocket是一种非常合适的技术选择。然而,在实际使用WebSocket的过程中,我们可能会遇到连接频繁断开的问题。最近,我在一个项目中就遇到了这样的问题,经过一番探索和优化,终于找到了解决方案,现在与大家分享一下。问题背景在项目......
  • 头歌实训作业 算法设计与分析-贪心算法(第2关:最优装载问题)
    任务描述有一批集装箱要装上一艘载重量为C的轮船,共有n个集装箱,其中集装箱i的重量为Wi。最优装载问题要求确定在装载体积不受限制的情况下,将尽可能多的集装箱装上轮船。测试说明输入和输出说明:第1行为集装箱数目n和载重限制C第2行~第n+1行为n个集装箱的重量输出最优装载......
  • 颜色分配问题
    要求我现在有N组数据,我现在想给他们每组分配一个6位的颜色代码,要求尽量分散。用什么方法分配比较合适?方案方法一:基于HSL(色相、饱和度、亮度)生成颜色HSL是一种颜色模型,其中色相(Hue)是一个角度值(0°到360°),可以很好地控制颜色的分散性。通过将色相均匀分布,可以生成一组分散的......
  • 问题8:yum报错:Loaded plugins: fastestmirror Loading mirror speeds from cached hos
    1.问题详情2.解决流程entOS-Base.repo的配置内容如下1[base]2name=CentOS-$releasever-Base3baseurl=http://vault.centos.org/7.9.2009/os/$basearch/4enabled=15gpgcheck=16gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-778[updates]9n......
  • 语音播报,套件多少异常的问题。(含源代码)
    在工作中遇到一家工厂老板的需求:因为产品是有多个配件组成,在生产的时候,经常会多生产,少生产,在组装时,也会出现配件多少的问题,现就此问题设计一款程序。多出,少的,异常的,正常好,会开语音播报。现将全部代码给出以备。 importinspectimportosimportthreadingimporttimeimpor......
  • [Qt]系统相关-多线程、线程安全问题以及线程的同步机制
    目录一、Qt多线程编程1.介绍2.多线程的操作线程的创建QThread的常用API使用案例3.Qt线程的使用场景二、线程安全问题1.互斥锁介绍使用案例2.读写锁三、线程的同步1.条件变量2.信号量一、Qt多线程编程1.介绍    Qt中的多线程的底层原理和注意事项等......
  • Redis的三大常见问题
    Redis的三大常见问题如果是一名能够熟练的将Redis运用到项目中的程序员,那么一定听说过Redis在使用中存在的问题,那么我们今天就来聊聊Redis的三大问题为什么会有三大问题?首先,对于很多刚接触Redis的同学,很多时候分不清Redis的作用,不太理解为什么要在SQL之外单独在搞一个Red......
  • 【读书与思考】从经济学和自由的角度看待若干爱情问题-纯爱战神、捞女、分手等
    【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】【AI日记】【读书与思考】引言快过年了,很多人可能会碰到相亲这个问题,加上最近看的一些书、短视频和新闻引发了我对于这方面的一些思考。纯爱战神与捞女之前刷到过纯爱战神或者捞女这种新闻,我想从下面几个角度谈......
  • 图论-岛屿数量问题
    代码随想录笔记-图论岛屿问题内容:题目描述:LeetCode 200.岛屿数量-力扣(LeetCode)(图源自代码随想录官网)本题是经典的图论遍历问题,只要能完成遍历即可,核心在于只要发现一块地,那么标记一下已经走过即可,以上图例输出为 3  -DFS深度优先搜索方法:每次......