前言
模数打错导致在模拟赛时变成40分
题目相关
题目大意
给定一个多边形的三角划分
定义一个旋转操作为对于四个有边相连的点
a
<
b
<
c
<
d
a<b<c<d
a<b<c<d,原本
a
↔
c
a\leftrightarrow c
a↔c连边,旋转后
b
↔
d
b\leftrightarrow d
b↔d连边
给定一幅
n
n
n个点的多边形三角划分图,求至少旋转多少次才能使这个图内不能再旋转,并输出这样旋转的方案数
给出的图分为两种,一种是原图,还有一种是在原图中给出一条边
a
↔
c
a\leftrightarrow c
a↔c,将其旋转得到的图(显然一条边的旋转唯一),询问图的数量为
m
m
m
数据范围
n , m ≤ 100000 n,m\le100000 n,m≤100000
题解
首先定义 d i d_i di为 i i i号点的度数(不包含多边形上的边)
首先考虑终止状态,观察样例,我们发现所有边都连向
n
n
n号点时就不能再转了
我们发现每次肯定能将一条端点中不含
n
n
n的边转到含
n
n
n
具体操作是选定一条边
a
↔
b
a\leftrightarrow b
a↔b,其两侧的三角形分别为
Δ
a
b
c
\Delta abc
Δabc和
Δ
a
b
n
\Delta abn
Δabn这样就可以把边转为
c
↔
n
c\leftrightarrow n
c↔n
我们发现这样一来,第一问的答案就是
n
−
3
−
d
n
n-3-d_n
n−3−dn
我们考虑第二问,首先,我们发现边的可旋转顺序构成的拓扑图是一棵树(这一点画画图就知道了)
然后我们考虑方案数如何计算
我们仔细想发现这是经典套路:[loj6391][THUPC2018]淘米神的树(Tommy)
我们考虑一棵树的子树方案数方案数(这棵子树的根一定要第一个做,剩下的按子树内部考虑即可)
a
n
s
u
=
(
s
i
z
e
u
−
1
)
!
∏
f
a
v
=
u
a
n
s
v
s
i
z
e
v
!
ans_u=(size_u-1)!\prod_{fa_v=u}\frac{ans_v}{size_v!}
ansu=(sizeu−1)!fav=u∏sizev!ansv
进行整理
a
n
s
u
=
(
s
i
z
e
u
−
1
)
!
∏
f
a
v
=
u
s
i
z
e
v
!
∏
f
a
v
=
u
a
n
s
v
ans_u=\frac{(size_u-1)!}{\prod_{fa_v=u}size_v!}\prod_{fa_v=u}{ans_v}
ansu=∏fav=usizev!(sizeu−1)!fav=u∏ansv
根节点额外考虑,根节点是
a
n
s
r
o
o
t
=
s
i
z
e
r
o
o
t
!
∏
f
a
v
=
r
o
o
t
a
n
s
v
s
i
z
e
v
!
ans_{root}=size_{root}!\prod_{fa_v=root}\frac{ans_v}{size_v!}
ansroot=sizeroot!fav=root∏sizev!ansv
代入,我们发现每个节点都是一个节点的儿子,也是一个自己,这样的话每个节点的贡献就是
(
s
i
z
e
u
−
1
)
!
s
i
z
e
u
=
1
s
i
z
e
u
\frac{(size_u-1)!}{size_u}=\frac1{size_u}
sizeu(sizeu−1)!=sizeu1
a
n
s
r
o
o
t
=
s
i
z
e
r
o
o
t
!
∏
u
≠
r
o
o
t
s
i
z
e
u
ans_{root}=\frac{size_{root}!}{\prod_{u\neq root} size_u}
ansroot=∏u̸=rootsizeusizeroot!
我们发现一条边
u
↔
v
u\leftrightarrow v
u↔v代表节点的size就是
∣
u
−
v
∣
−
1
|u-v|-1
∣u−v∣−1
设
a
n
s
1
ans_1
ans1为第一问的答案,
a
n
s
2
ans_2
ans2为第二问的答案
即
a
n
s
2
=
a
n
s
1
!
∏
u
↔
v
,
u
≠
n
,
v
≠
n
∣
u
−
v
∣
−
1
ans_2=\frac{ans_1!}{\prod_{u\leftrightarrow v,u\neq n,v\neq n}|u-v|-1}
ans2=∏u↔v,u̸=n,v̸=n∣u−v∣−1ans1!
我们发现这个很好维护,只要找到一次旋转从哪条边转到了哪条边即可,用个set维护一下每个点的出边,然后在set中找连边目标点的编号相邻元素就是了
代码
代码很清真
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<set>
#include<vector>
typedef long long ll;
#define rg register
template <typename T> inline void read(T&x){char cu=getchar();x=0;bool fla=0;while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}while(isdigit(cu))x=x*10+cu-'0',cu=getchar();if(fla)x=-x;}
template <typename T> inline void printe(const T x){if(x>=10)printe(x/10);putchar(x%10+'0');}
template <typename T> inline void print(const T x){if(x<0)putchar('-'),printe(-x);else printe(x);}
template <typename T> inline T gcd(const T a,const T b){if(!b)return a;return gcd(b,a%b);}
template <typename T> inline T min(const T a,const T b){return a<b?a:b;}
template <typename T> inline T abs(const T a){return a>0?a:-a;}
const int maxn=100001,mod=1000000007;
int W,n,m,d[maxn];
std::set<int>e[maxn];
ll pow(ll x,ll y)
{
ll res=1;
for(;y;y>>=1,x=x*x%mod)if(y&1)res=res*x%mod;
return res;
}
ll ans,fac[maxn],inv[maxn];
ll C(const int n,const int m){return fac[n]*inv[m]%mod*inv[n-m]%mod;}
void calc()
{
print(n-3-d[n]);
if(W)putchar(' '),print(fac[n-3-d[n]]*pow(ans,mod-2)%mod);
putchar('\n');
}
inline void del(const int u,const int v)
{
d[u]--,d[v]--;
if(u!=n&&v!=n)ans=ans*pow(abs(u-v)-1ll,mod-2)%mod;
}
inline void add(const int u,const int v)
{
d[u]++,d[v]++;
if(u!=n&&v!=n)ans=ans*(abs(u-v)-1)%mod;
}
int main()
{
// freopen("polygon.in","r",stdin);freopen("polygon.out","w",stdout);
fac[0]=1;
for(rg int i=1;i<=100000;i++)fac[i]=fac[i-1]*i%mod;
inv[100000]=pow(fac[100000],mod-2);
for(rg int i=100000;i>=1;i--)inv[i-1]=inv[i]*i%mod;
ans=1;
read(W),read(n);
for(rg int i=1;i<=n-3;i++)
{
int u,v;read(u),read(v);
e[u].insert(v),e[v].insert(u);
add(u,v);
}
e[n].insert(1),e[1].insert(n);
for(rg int i=1;i<n;i++)e[i].insert(i+1),e[i+1].insert(i);
calc();
read(m);
for(rg int i=1;i<=m;i++)
{
int a,b,na,nb;read(a),read(b);
del(a,b);
std::set<int>::iterator Pos=e[a].find(b),Posl,Posr;
Posl=Pos,Posl--;
Posr=Pos,Posr++;
na=*Posl,nb=*Posr;
add(na,nb);
calc();
del(na,nb);
add(a,b);
}
return 0;
}
总结
要想到拓扑的结果是一棵树,然后推一波式子就好了
好像还可以用splay搞,听说本质相同