「AGC028D」Chords 题解
题目简介
给定一个圆, 圆上均等地放着 \(2N\) 个点, 已有 \(K\) 对点之间连好了线段, 从中选择剩下 \(N−K\) 对点随意连线段(每个点只连一条线段)。
两点联通当且仅当两点在同一条线段上或两点所属于的线段相交, 求所有连边方案中, 联通块的个数和。
分析
将圆从 \(1\) 到 \(2N\) 线性展开,发现规律:倘若弦 \(AB\)、\(CD\) 相交,当且仅当数轴上的线段 \(AB\)、\(CD\) 相交且不包含。
经过观察(读者可以画一画图),不难发现,一个连通块在数轴上一定对应了一个区间,满足该连通块完全在这个区间内,而且区间端点属于连通块(否则有点连到连通块外面,一定会与连通块内部的弦有交,连通块会变大)。但是要注意这个区间内并非全都是这个连通块,可能其中还有小连通块。
记 \(p[x]\) 为题目中已连边的点 \(x\) 的对应点。
又记\(f[l][r]\) 为区间 \([l,r]\) 为一个连通块,且 \(l,r\) 属于这个连通块的方案数。根据上述,我们可以发现,\(f[l][r]\) 有意义的前提是\(\forall x\in[l,r],\ p[x] \in [l,r]\)
记 \(g[i]\) 表示 \(i\) 个点随意连的方案数, \(h(l,r)\) 表示 \([l,r]\) 内没有被钦定的点数。
打表找规律:
所以说,如果没有“ \(l,r\) 属于同一个连通块 ”的限制的话,那么 \(f[l][r]=g[h(l,r)]\)。
现在需要除掉 \(l\) 的右端点不是 \(r\) 的情况,可以枚举 \(r\) 的右端点 \(i\) ,减去以 \(i\) 为右端点的贡献,及 \(f[l][i]\times g[h(i+1,r)]\)
总的来说:
最后,统计答案,即统计连通块的贡献之和:
\(AC\ Code\)
#include<cstdio>
#include<iostream>
using namespace std;
const int Mod=1e9+7;
int f[605][605];
int g[605],p[605],c[605];
int ans;
inline int h(int l,int r){return c[r]-c[l-1];}
void solve(int l,int r,int n){
for(int i=l;i<=r;++i)
if(p[i]&&p[i]<l||p[i]>r)return ;
f[l][r]=g[h(l,r)];
for(int i=l+1;i<r;++i)
f[l][r]=(f[l][r]-1ll*f[l][i]*g[h(i+1,r)]%Mod+Mod)%Mod;
ans+=1ll*f[l][r]*g[h(1,l-1)+h(r+1,n)]%Mod;
ans%=Mod;
}
int main(){
int n,m;cin>>n>>m;
n<<=1;g[0]=1;
for(int i=2;i<=n;i+=2)g[i]=1ll*g[i-2]*(i-1)%Mod;
for(int i=1;i<=m;++i){
int x,y;cin>>x>>y;
p[x]=y,p[y]=x;
}
for(int i=1;i<=n;++i)c[i]=c[i-1]+(!p[i]);
for(int i=1;i<=n;++i)
for(int j=i+1;j<=n;j+=2)solve(i,j,n);
cout<<ans<<'\n';
return 0;
}