【HNOI2019】多边形(二叉树)(组合数学)

传送门


题解:

想到了以前做过的一道树上标号分配的题,然后就迅速切掉这道题了。

少见的连我都能切的HNOI题了。

很容易发现终止状态是所有对角线全部指向 n n n的情况。

也很容易发现最少步数为 n − 1 − 所 有 与 n 直 接 连 接 的 边 数 n-1-所有与n直接连接的边数 n1n,这里的边数包括 1 1 1 n − 1 n-1 n1

那么要求就是每一步操作必须把一个原来没有和 n n n连接的边改成和 n n n连接。

发现在初始状态下有的边不能直接旋转自己与 n n n连接,也就意味着操作有一定的顺序要求。

手玩一下发现顺序限制是一个二叉树森林的形式,每个节点代表多边形的一条对角线,父亲的操作必须在孩子操作之前。

这一部分是非常SB的组合数学。

然后考虑初始旋转一条边产生的影响。

如果这条边对应的点是二叉树森林中的一个根,则实际的影响是删这个根,然后将两个子树作为新树。最少操作次数-1,方案数还是一样的组合数学。

否则,把二叉树画出来,我们发现它的实际影响是一个Rotate,可以暴力模拟,也可以推一下改变的系数。

这道题的组合数学部分挺SB的,只要看出来树上标号的性质就会做了。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define gc get_char
#define cs const

namespace IO{
	inline char get_char(){
		static cs int Rlen=1<<22|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	
	template<typename T>
	inline T get(){
		char c;
		while(!isdigit(c=gc()));T num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
	inline int gi(){return get<int>();}
}
using namespace IO;

using std::cerr;
using std::cout;

cs int mod=1e9+7;
inline int mul(int a,int b){ll r=(ll)a*b;return r>=mod?r%mod:r;}
inline void Mul(int &a,int b){a=mul(a,b);}

cs int N=2e5+7;

int fac[N],inv[N],ifac[N];
inline void init_inv(){
	fac[0]=fac[1]=inv[0]=inv[1]=ifac[0]=ifac[1]=1;
	for(int re i=2;i<N;++i){
		fac[i]=mul(fac[i-1],i);
		inv[i]=mul(mod-mod/i,inv[mod%i]);
		ifac[i]=mul(ifac[i-1],inv[i]);
	}
}

inline int C(int n,int m){return mul(fac[n],mul(ifac[m],ifac[n-m]));}
inline int iC(int n,int m){return mul(ifac[n],mul(fac[m],fac[n-m]));}
inline int calc(int n,int m){return C(n+m,m);}
inline int icalc(int n,int m){return iC(n+m,m);}

int W,n,m,ans=1,cur_ans,cnt,cur_cnt;
std::vector<int> E[N];
std::unordered_map<ll,int> _id;
int rt[N],siz[N],fa[N],son[N][2],now;

void build_binary(int &u,int l,int r,int p=0){
	if(l+1>=r)return ;
	siz[u=++now]=1,fa[u]=p;
	_id[(ll)N*l+r]=u;
	int m=*upper_bound(E[r].begin(),E[r].end(),l);
	build_binary(son[u][0],l,m,u);
	build_binary(son[u][1],m,r,u);
	siz[u]=siz[son[u][0]]+siz[son[u][1]]+1;
	Mul(ans,calc(siz[son[u][0]],siz[son[u][1]]));
}

signed main(){
#ifdef zxyoi
	freopen("poly.in","r",stdin);
#endif
	init_inv();
	W=gi(),n=gi();
	for(int re i=2;i<n;++i)E[i].push_back(i-1),E[i].push_back(i+1);
	E[1].push_back(2),E[1].push_back(n);
	E[n].push_back(n-1),E[n].push_back(1);
	for(int re i=1;i<=n-3;++i){
		int u=gi(),v=gi();
		E[u].push_back(v);
		E[v].push_back(u);
	}
	for(int re i=1;i<=n;++i)std::sort(E[i].begin(),E[i].end());
	for(int re i=0;i+1<E[n].size();++i)build_binary(rt[i],E[n][i],E[n][i+1]);
	int tot_siz=0;
	for(int re i=0;i+1<E[n].size();tot_siz+=siz[rt[i++]])Mul(ans,calc(tot_siz,siz[rt[i]]));
	cur_cnt=cnt=n-1-E[n].size();cur_ans=ans;
	if(W)cout<<cur_cnt<<" "<<cur_ans<<"\n";else cout<<cur_cnt<<"\n";
	m=gi();
	while(m--){
		int l=gi(),r=gi();if(l>r)std::swap(l,r);
		int u=_id[(ll)N*l+r];
		cur_cnt=cnt-!fa[u];
		if(!W){cout<<cur_cnt<<"\n";continue;}
		cur_ans=ans;
		if(!fa[u]){
			int lc=siz[son[u][0]],rc=siz[son[u][1]];
			Mul(cur_ans,icalc(lc,rc));
			Mul(cur_ans,icalc(tot_siz-siz[u],siz[u]));
			Mul(cur_ans,calc(tot_siz-siz[u],lc));
			Mul(cur_ans,calc(tot_siz-rc-1,rc));
		}
		else {
			int d=son[fa[u]][1]==u,s1=siz[son[u][d]],s2=siz[son[u][!d]],s3=siz[son[fa[u]][!d]];
			Mul(cur_ans,icalc(s1,s2));
			Mul(cur_ans,icalc(siz[u],s3));
			Mul(cur_ans,calc(s1,s2+s3+1));
			Mul(cur_ans,calc(s2,s3));
		}
		cout<<cur_cnt<<" "<<cur_ans<<"\n";
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值