题解:P10230 [COCI 2023/2024 #4] Lepeze

题解: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(nlog⁡n)O(n \log n)O(nlogn)。

代码

cpp 复制代码
#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;
}
相关推荐
Geek-Chow1 小时前
06 训练管线:数据如何变成权重
人工智能·算法
我变成萤火虫1 小时前
反悔贪心(经典题目讲解)
c++·算法·贪心算法·stl·排序算法·反悔贪心
_Narcissus_2 小时前
排序算法总结表格
c语言·数据结构·c++·笔记·算法·排序算法·总结
一只小小的芙厨3 小时前
前缀和与差分
数据结构·算法
船厂电气自动化ai大模型3 小时前
AI大模型与数学第42课:泰勒级数完整展开(神经网络近似核心)
人工智能·python·深度学习·算法·机器学习
从琳开始9893 小时前
优选算法——双指针(算法原理+力扣题)
算法·leetcode·职场和发展
从琳开始9893 小时前
优选算法——滑动窗口(概念+解题模板+LeetCode例题讲解)
算法·leetcode·职场和发展
Nil2083 小时前
leetcode 94二叉树的中序遍历
算法·leetcode·职场和发展
兔兔兔兔13 小时前
记录C++ 8
开发语言·c++