【洛谷5288】[HNOI2019] 多边形(二叉树模型)
大致题意: 给你一个多边形,用若干不重合、不相交的线段将其划分为若干三角形区域,并定义旋转操作\((a,c)\)为选定\(4\)个点\(a,b,c,d\)满足\(a<b<c<d\)且\(ab,ac,ad,bc,cd\)有连边,然后删去边\(ac\)并加上边\(bd\)。带修独立询问至少旋转几次使得无法继续旋转。
关于无法继续旋转
第一次看完题面,我是一脸懵逼:选中四个点不可以无限重复旋转吗?为什么会无法旋转?
然后冷静了一下重新看了遍题面,才发现\(a,b,c,d\)是有序的。。。
再看样例解释可以发现,似乎最终状态就是每个点都向\(n\)号节点连边?(似乎也挺好证明的)
至此才算勉强明白了题目在讲什么。
接下来,我们先考虑没有修改、单组询问怎么做。
处理第一个询问
不难发现,第一个询问还是比较简单的。
首先易知,每一次旋转操作,我们都可以将某条原本并不连向\(n\)的边变成连向\(n\)的边。
那么,根据贪心,有多少条边不与\(n\)相连,答案就是多少。
实现似乎也不是很难?
前置知识:二叉树模型
在处理第二个询问之前,我们需要知道,这题涉及到一个叫“二叉树模型”的东西。
由于我们之前第一个询问确定答案的方式,易知,在一条边连向\(n\)之后,我们就不会再去旋转它了(不然肯定不优)。
又因为题目中给出了边不重复、不相交的条件,且旋转操作是不会使边相交的,因此我们可以发现,每一条连向\(n\)的边,都会把图给分成两部分。
而我们要知道一个结论:在图的每一部分中,有且仅有一条不与\(n\)相连的边可以旋转成与\(n\)相连的边,也就是说每次旋转的边是唯一确定的。
再结合前面提到过的二叉树模型,那么我们就可以考虑,建一棵二叉树,即把当前旋转的边作为父亲,被分成的两部分作为左右儿子。
于是我们就建成了一棵二叉树。
然后考虑对于每个被多边形中原有的边划分出的一部分,我们都需要建一棵二叉树,因此就会建成若干棵二叉树。
利用这些二叉树处理询问就简单了许多。
处理第二个询问
现在我们来考虑如何处理第二个询问。
我们在二叉树上从下往上讨论,对于叶子节点,显然它只有一种旋转方式,即旋转它本身。
否则,对于二叉树中有两个子树大小分别为\(x\)和\(y\)的儿子的节点,我们这样考虑它的方案:
- 首先,子节点的子树内部的顺序,肯定在以该子节点为根时已经计算过了。所以,我们只需考虑子树间的顺序。
- 那么,问题就相当于归并两个长度分别为\(x\)和\(y\)的有序数列的方案数。这是一个经典的组合问题,答案就是\(C_{x+y}^x\)。
- 综上所述,每次合并节点时,我们将方案数乘上\(C_{x+y}^x\)即可。
最后,我们再来考虑下每棵二叉树根节点之间的贡献。
这其实也挺简单的吧,就是在枚举根的同时记录先前枚举过的二叉树的总\(Size\),然后考虑和前面一样的计算方式即可。
这样我们就成功求解了答案
处理多组询问
考虑每次只旋转一条边,且询问独立。
对于第一个询问,我们只要判断旋转的这条边在二叉树中是否有父亲,如果没有,说明旋转它可以向\(n\)连边,因此将答案减\(1\)。否则答案不变。
对于第二个询问,其实也就可以理解为在二叉树上修改一对父子关系,有点类似于\(Treap\)和\(Splay\)中的\(Rotate\)操作吧。
但注意要对于修改的节点是否为一棵二叉树的根进行分类讨论:
如果这个节点不是一棵二叉树的根,首先,我们除去它两个子节点合并的贡献以及它与其兄弟合并的贡献(即其父亲两个子节点合并的贡献)。
要加上的贡献就略复杂了。
若用\(sz_i\)表示以\(i\)为根的子树大小,并记该节点为\(x\),其父节点为\(f\),并且\(x\)是\(f\)的\(d\)儿子(\(0\)为左,\(1\)为右),则要加上的贡献可推得:
我们先将\(x\)原先的\(d\text{^}1\)儿子现在与\(f\)的\(d\text{^}1\)儿子计算贡献,然后再将\(f\)子树总\(Size\)除去\(x\)及其\(d\)儿子\(Size\)后再一同与\(x\)的\(d\)儿子的\(Size\)计算贡献。
\(x\)为根类似,只不过是要考虑其他二叉树的总\(Size\)与\(x\)子节点\(Size\)的合并。
这里的具体实现可见代码。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define X 1000000007
#define swap(x,y) (x^=y^=x^=y)
#define pb push_back
using namespace std;
int n,ty,Fac[N+5],IFac[N+5];vector<int> s[N+5];
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define pc(c) (C^FS?FO[C++]=c:(fwrite(FO,1,C,stdout),FO[(C=0)++]=c))
#define tn (x<<3)+(x<<1)
#define D isdigit(c=tc())
int T,C;char c,*A,*B,FI[FS],FO[FS],S[FS];
public:
I FastIO() {A=B=FI;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
Tp I void write(Con Ty& x,Con char& y) {write(x),pc(y);}
I void clear() {fwrite(FO,1,C,stdout),C=0;}
}F;
class BinaryTreeSolver//二叉树模型
{
private:
#define LB lower_bound
#define GV(x,y) (1LL*Fac[(x)+(y)]*IFac[x]%X*IFac[y]%X)//求值,就是一个组合数
#define GIV(x,y) (1LL*IFac[(x)+(y)]*Fac[x]%X*Fac[y]%X)//除去贡献,直接用逆元,避免多一个log
#define GV_S(x) GV(O[O[x].S[0]].Sz,O[O[x].S[1]].Sz)//求合并两儿子的值
#define GIV_S(x) GIV(O[O[x].S[0]].Sz,O[O[x].S[1]].Sz)//求合并两儿子的值的逆元
int w,v,g,tot,rt[N+5],sz[N+5],S[N+5][2];struct node {int Sz,F,S[2];}O[N+5];
struct Pr
{
int l,r;I Pr(CI a=0,CI b=0):l(a),r(b){}
I bool operator < (Con Pr& o) Con {return l^o.l?l<o.l:r<o.r;}
};map<Pr,int> p;
I void SU(CI l,CI r,int& rt,CI lst=0)//建立二叉树
{
if(l+2>r) return;RI mid=s[r][LB(s[r].begin(),s[r].end(),l+1)-s[r].begin()];//如果只剩左右端点,返回,否则用lower_bound找到二叉树父节点
O[p[Pr(l,r)]=rt=++tot].F=lst,SU(l,mid,O[rt].S[0],rt),SU(mid,r,O[rt].S[1],rt),
O[rt].Sz=O[O[rt].S[0]].Sz+O[O[rt].S[1]].Sz+1,v=1LL*v*GV_S(rt)%X;//建树
}
public:
I BinaryTreeSolver() {v=1;}
I void Init(CI x,CI l,CI r) {SU(l,r,rt[x]),v=1LL*v*GV(g,O[rt[x]].Sz)%X,g+=O[rt[x]].Sz;}//初始化
I void PWork() {w=n-1-s[n].size(),ty?(F.write(w,' '),F.write(v,'\n')):F.write(w,'\n');}//求初始答案
I void UWork(CI x,CI y)//处理带修独立询问
{
RI k=p[Pr(x,y)],f=O[k].F,d=O[f].S[1]==k;
if(!ty) return F.write(w-(!f),'\n');F.write(w-(!f),' ');//对于无需第二个询问直接输出并退出函数
if(f)//如果不是根
{
RI t=1LL*v*GIV_S(k)%X*GIV_S(f)%X*GV(O[O[f].S[d^1]].Sz,O[O[k].S[d^1]].Sz)%X;
F.write(1LL*t*GV(O[f].Sz-O[k].Sz+O[O[k].S[d^1]].Sz,O[O[k].S[d]].Sz)%X,'\n');
}
else//如果是根
{
RI t=1LL*v*GIV_S(k)%X*GIV(g-O[k].Sz,O[k].Sz)%X*GV(g-O[k].Sz,O[O[k].S[0]].Sz)%X;
F.write(1LL*t*GV(g-O[k].Sz+O[O[k].S[0]].Sz,O[O[k].S[1]].Sz)%X,'\n');
}
}
}T;
I int Qpow(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
int main()
{
RI Qtot,i,x,y;for(F.read(ty,n),i=1;i<=n-3;++i) F.read(x,y),s[x].pb(y),s[y].pb(x);//读入
for(s[n].pb(1),i=1;i^n;++i) s[i].pb(i+1);for(s[1].pb(n),i=2;i<=n;++i) s[i].pb(i-1);//处理边
for(i=1;i<=n;++i) sort(s[i].begin(),s[i].end());//排序
for(Fac[0]=i=1;i<=n;++i) Fac[i]=1LL*Fac[i-1]*i%X;//预处理阶乘
for(IFac[n]=Qpow(Fac[n],X-2),i=n-1;~i;--i) IFac[i]=1LL*IFac[i+1]*(i+1)%X;//预处理阶乘逆元
for(x=s[n].size(),i=0;i^(x-1);++i) T.Init(i,s[n][i],s[n][i+1]);T.PWork();//初始化
F.read(Qtot);W(Qtot--) F.read(x,y),x>y&&swap(x,y),T.UWork(x,y);//求答案
return F.clear(),0;
}
【洛谷5288】[HNOI2019] 多边形(二叉树模型)的更多相关文章
- 【洛谷】P1040 加分二叉树
[洛谷]P1040 加分二叉树 题目描述 设一个n个节点的二叉树tree的中序遍历为(1,2,3,…,n),其中数字1,2,3,…,n为节点编号.每个节点都有一个分数(均为正整数),记第i个节点的分数 ...
- 题解 洛谷P5018【对称二叉树】(noip2018T4)
\(noip2018\) \(T4\)题解 其实呢,我是觉得这题比\(T3\)水到不知道哪里去了 毕竟我比较菜,不大会\(dp\) 好了开始讲正事 这题其实考察的其实就是选手对D(大)F(法)S(师) ...
- [HNOI2019]多边形[二叉树建模、组合计数]
题意 题目链接 分析 不难发现终态一定是 \([2,n-2]\) 中的每个点都与 \(n\) 连边. 关于凸多边形的划分问题,可以将它看作一棵二叉树:每个树点可以看做点可以看做边. 本题中看做点来处理 ...
- 【洛谷P5018】对称二叉树
题目大意:定义对称二叉树为每个节点的左右子树交换后与原二叉树仍同构的二叉树,求给定的二叉树的最大对称二叉子树的大小. 代码如下 #include <bits/stdc++.h> using ...
- 【洛谷P3884 [JLOI2009]】二叉树问题
题目描述 如下图所示的一棵二叉树的深度.宽度及结点间距离分别为: 深度:4 宽度:4(同一层最多结点个数) 结点间距离: ⑧→⑥为8 (3×2+2=8) ⑥→⑦为3 (1×2+1=3) 注:结点间距离 ...
- 洛谷P1087--FBI树(二叉树)
题目描述 我们可以把由“0”和“1”组成的字符串分为三类:全“0”串称为B串,全“1”串称为I串,既含“0”又含“1”的串则称为F串. FBI树是一种二叉树,它的结点类型也包括F结点,B结点和I结点三 ...
- 洛谷P5292 [HNOI2019]校园旅行(二分图+最短路)
题面 传送门 题解 如果暴力的话,我们可以把所有的二元组全都扔进一个队列里,然后每次往两边更新同色点,这样的话复杂度是\(O(m^2)\) 怎么优化呢? 对于一个同色联通块,如果它是一个二分图,我们只 ...
- 题解【洛谷P3884】[JLOI2009]二叉树问题
题面 题解 这道题目可以用很多方法解决,这里我使用的是树链剖分. 关于树链剖分,可以看一下我的树链剖分学习笔记. 大致思路是这样的: 第\(1\)次\(dfs\)记录出每个点的父亲.重儿子.深度.子树 ...
- [洛谷P1040] 加分二叉树
洛谷题目链接:加分二叉树 题目描述 设一个n个节点的二叉树tree的中序遍历为(1,2,3,-,n),其中数字1,2,3,-,n为节点编号.每个节点都有一个分数(均为正整数),记第i个节点的分数为di ...
随机推荐
- PIE SDK栅格分级渲染
1. 功能简介 栅格数据分级渲染是根据不同的分级规则,对像元值进行等级划分:并通过对每一级设置不同的显示符号和标注信息,从而达到分级显示的效果. 2.功能实现说明 2.1. 实现思路及原理说明 第一 ...
- Mastering the Game of Go 论文阅读笔记
主要思想:用状态评估减少搜索深度,用动作采样减少搜索宽度. 参考文献:https://blog.csdn.net/songrotek/article/details/51065143
- nodejs基础知识查缺补漏
1. 单线程.异步I/O.对比php nodejs是单线程的,但是是异步I/O,对于高并发时,它也能够快速的处理请求,100万个请求也可以承担,但是缺点是非常的耗内存,但是我们可以加大内存, 所以能用 ...
- nyoj 1239——引水工程——————【最小生成树 prim】
引水工程 时间限制:2000 ms | 内存限制:65535 KB 难度:3 描述 南水北调工程是优化水资源配置.促进区域协调发展的基础性工程,是新中国成立以来投资额最大.涉及面最广的战略性工 ...
- BNU 28887——A Simple Tree Problem——————【将多子树转化成线段树+区间更新】
A Simple Tree Problem Time Limit: 3000ms Memory Limit: 65536KB This problem will be judged on ZJU. O ...
- 11、幻灯片:Slides
/* ---ts----*/ import { Page,Slides } from 'ionic-angular'; import { ViewChild } from '@angular/core ...
- VueConf 全球首届Vue.js开发者大会资料整理
最近一直关注VueConf全球首届Vue.js开发者大会,现在将此次开发者大会资料整理如下: 一.Vue 2017 现状与展望 [尤雨溪] 在线视频: PPT整理: Vue 2017 现状与展望 ...
- CentOS-7 本地yum源挂载
在Linux无法连接到互联网时,手动安装依赖是及其麻烦的一件事,需要花费大量的时间寻找rpm包.但在配置本地yum源后,绝决依赖问题就会变得非常简单. 一.准备 centos-7.ISO镜像文件: 二 ...
- js之正则表达式基础
字符串是编程时涉及到的最多的一种数据结构,对字符串进行操作的需求几乎无处不在.比如判断一个字符串是否是合法的Email地址,虽然可以编程提取@前后的子串,再分别判断是否是单词和域名,但这样做不但麻烦, ...
- Spring课程 Spring入门篇 5-3 配置切入点 pointcut
1 解析 1.1 xml常见的配置切入点写法 2 代码演练 2.1 xml配置切入点 1 解析 1.1 xml常见的配置切入点写法 2 代码演练 2.1 xml配置切入点 xml配置: <? ...