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

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

题意

给定一个正 nnn 边形的三角剖分,一次操作指删去三角剖分的一条边再加入另一条边。

查询 qqq 次,两种查询,一种是进行一次操作,另一种是询问让三角剖分的边全部与点 xxx 相连时的最少操作数及其操作方案数。

思路

首先注意到每次操作实际上是选择三角剖分中的一个四边形,将其对角线互换。

那么设未与 xxx 相连的边为 ppp,则最少操作数即为 ppp(三角剖分原本有 n−3n-3n3 条边),即每一次操作都可以让一条边与 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-an2a 的点,则相当于对 aaa 个点有 1n−2−a\frac{1}{n-2-a}n2a1 的贡献(有 n−2−an-2-an2a 个三角形),反之亦然。

以上就是加边的逻辑,反之就是减边。由于需要维护区间乘和单点差,开一个树状数组即可,时间复杂度 O(nlog⁡n)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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值