题解 [AGC028D] Chords

传送门

一眼比较可做:转化成枚举连通块,求使这个连通块恰好为一个连通块的方案数就好了
然后想当然的把 枚举连通块 这个过程认为成了枚举区间 \([l, r]\),钦定 \([l, r]\) 中的所有点恰好形成一个连通块
然而 \((1, 4), (2, 3)\) 这样的连边方式可以形成两个连通块,而 \(1,4\) 这个连通块又不满足上面那个条件
于是就各种假
于是爬去康题解
发现每个连通块唯一对应一个 \([l, r]\) 满足 \(\forall i\in [l, r],to_i\in[l, r]\),且 \(l, r\) 在同一个连通块内
前一个条件是容易满足的,后一个条件枚举 \(l\) 所在连通块最靠右的点容斥即可

\[f_{i, j}=任意连边方案数-\sum\limits_{k=i+1}^{j-1}f_{i, k}\times(k+1, j任意连边方案数) \]

于是可以做到 \(O(n^3)\)

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 610
#define ll long long
//#define int long long

char buf[1<<21], *p1=buf, *p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
inline int read() {
	int ans=0, f=1; char c=getchar();
	while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
	while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
	return ans*f;
}

int n, k;
ll f[N][N], g[N], ans;
const ll mod=1e9+7;
int a[N], b[N], e[N], p[N], cnt[N];

signed main()
{
	n=read()<<1; k=read();
	for (int i=1; i<=k; ++i) {
		a[i]=read(), b[i]=read();
		e[a[i]]=b[i], e[b[i]]=a[i];
	}
	g[0]=1;
	for (int i=1; i<=n; ++i) g[i]=(2*i-1)*g[i-1]%mod;
	for (int j=1; j<=n; ++j) cnt[j]=cnt[j-1]+(e[j]==0);
	for (int i=1; i<=n; ++i) {
		// cout<<"i: "<<i<<endl;
		for (int j=i+1; j<=n; j+=2) {
			for (int k=i; k<=j; ++k) if (e[k] && (e[k]<i||e[k]>j)) goto jump;
			assert(!((cnt[j]-cnt[i-1])&1));
			f[i][j]=g[(cnt[j]-cnt[i-1])>>1];
			for (int k=i+1; k<j; k+=2) f[i][j]=(f[i][j]-f[i][k]*g[(cnt[j]-cnt[k])>>1])%mod;
			ans=(ans+f[i][j]*g[(cnt[n]-cnt[j]+cnt[i-1])>>1])%mod;
			jump: ;
		}
	}
	printf("%lld\n", (ans%mod+mod)%mod);
	
	return 0;
}
posted @ 2022-04-26 15:59  Administrator-09  阅读(2)  评论(0编辑  收藏  举报