题解:P10230 [COCI 2023/2024 #4] Lepeze
题意
给定一个正 nnn 边形的三角剖分,一次操作指删去三角剖分的一条边再加入另一条边。
查询 qqq 次,两种查询,一种是进行一次操作,另一种是询问让三角剖分的边全部与点 xxx 相连时的最少操作数及其操作方案数。
思路
首先注意到每次操作实际上是选择三角剖分中的一个四边形,将其对角线互换。

那么设未与 xxx 相连的边为 ppp,则最少操作数即为 ppp(三角剖分原本有 n−3n-3n−3 条边),即每一次操作都可以让一条边与 xxx 相连。
因为以 xxx 为顶点的边会划分为多个以 xxx 为顶点的四边形,如果有四边形的对角线如果没有与 xxx 相连,就可以用一次操作将对角线换为与 xxx 相连的边。

如图,一开始有两条边与 xxx 相连,可以划分出 3 个以 xxx 为顶点的四边形,那么我们可以通过三次操作将四边形内的对角线互换使得所有边都连向 xxx。
接下来要求的是方案数,注意到如果以每一个三角形为节点,向相邻的三角形连边,图最终会变为一棵树,而每次操作相当于从一个节点向另一个节点走,将他们之间的边连向 xxx。

去掉以 xxx 为顶点的三角形代表的灰色顶点整个图就变为了一个森林。
由于我们操作必须从以 xxx 为顶点的四边形向外扩展,所以操作的过程就必须从父节点向子节点走,可以看作一个拓扑序,答案就是求拓扑序的方案。

如图,一种可能的操作方案是 1->2->3->4->5,其中每个节点代表的边是与其父节点相邻的边被操作为与 xxx 相连的边。
现在就要看如何求拓扑序的方案数了。
已知树的节点数为 ppp,总排列方案为 p!p!p!,由于每个节点要在其子树节点前,所以每个节点在排列中有 1szi\frac{1}{sz_i}szi1 的概率在其子树节点前,所有节点的限制乘起来就是 1∏sz\frac{1}{\prod{sz}}∏sz1,总方案是就是 p!∏sz\frac{p!}{\prod{sz}}∏szp!。
现在主要的问题就是求 1∏sz\frac{1}{\prod{sz}}∏sz1 了。
注意到一条三角剖分的边实际上将整棵树分为了两部分。

如图,红色边剖出的蓝色部分的子树会对另一侧的三个点作为答案时有 1szi\frac{1}{sz_i}szi1 的贡献,其中 szi=3sz_i=3szi=3,所以当我们增加一条边 (l,r)(l,r)(l,r) 时,设一侧有 aaa 个点,另一侧有 n−2−an-2-an−2−a 的点,则相当于对 aaa 个点有 1n−2−a\frac{1}{n-2-a}n−2−a1 的贡献(有 n−2−an-2-an−2−a 个三角形),反之亦然。
以上就是加边的逻辑,反之就是减边。由于需要维护区间乘和单点差,开一个树状数组即可,时间复杂度 O(nlogn)O(n \log n)O(nlogn)。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
int n,q;
ll inv[200010],f[200010],ans[200010];
int lowbit(int x){
return x&(-x);
}
ll qpow(ll a,ll b){
ll ans=1;
for(;b;b>>=1,a=a*a%mod)if(b&1)ans=ans*a%mod;
return ans;
}
struct N{
ll tr[200010];
void add(int x,ll v){
if(!x)return ;
for(int i=x;i<=n;i+=lowbit(i)){
tr[i]=(tr[i]*v)%mod;
}
}
ll find(int x){
if(!x)return 1;
ll ans=1;
for(int i=x;i;i-=lowbit(i)){
ans=ans*tr[i]%mod;
}
return ans;
}
void change(int l,int r,ll v){
add(l,v);
add(r+1,qpow(v,mod-2));
if(l>r)add(1,v);
}
}tr;
void solve(int x,int y){//加边
ans[x]--;ans[y]--;
int len=(x-y+n)%n-1;
tr.change(x+1,y-1,inv[len]);
len=(y-x+n)%n-1;
tr.change(y+1,x-1,inv[len]);
}
void solve2(int x,int y){//减边
ans[x]++;ans[y]++;
int len=(x-y+n)%n-1;
tr.change(x+1,y-1,len);
len=(y-x+n)%n-1;
tr.change(y+1,x-1,len);
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>q;
f[0]=1;
for(int i=1;i<=n;i++){
inv[i]=qpow(i,mod-2);
tr.tr[i]=1;
f[i]=f[i-1]*i%mod;
ans[i]=n-3;
}
for(int i=1,x,y;i<=n-3;i++){
cin>>x>>y;
solve(x,y);
}
while(q--){
int op;
cin>>op;
if(op==1){
int a,b,c,d;
cin>>a>>b>>c>>d;
solve2(a,b);
solve(c,d);
}
else{
int x;
cin>>x;
cout<<ans[x]<<" "<<f[ans[x]]*tr.find(x)%mod<<'\n';
}
}
return 0;
}

222

被折叠的 条评论
为什么被折叠?



