AT4438 [AGC028D] Chords 题解

算法:类似区间DP
Solution:
考虑对于每个连通块,计算它对答案的贡献次数
对于一个连通块,用它包含的编号最小的和最大的来代表它
\(dp_{i,j}\)代表以\((i,j)\)为代表的连通块的贡献次数
首先(i,j)不可能有连向外面的边,不然就不可能用(i,j)代表,遇到这样的区间直接跳过
定义\(n\)个点任意连边,答案为\(g(n)\)
(其中当\(2\nmid n\)时,\(g(n)=0\),\(2|n\)\(g(n)=\prod\limits_{i\leq n,2\nmid n}i\)
考虑容斥,没有限制时答案为\(g(c)\),其中c为\((i,j)\)中还未连边的点的数量
再减掉不合法的情况,就是\((i,j)\)被从中切成了两个部分,\((i,k)\)\((k+1,j)\),互相没有连边
这个答案为\(dp_{i,k}\times g(d)\),其中\(d\)\((k+1,j)\)中还未连边的点的数量

加起来即可
答案为\(\sum\limits dp_{i,j}\times g(n-2k-c)\),其中c为\((i,j)\)中还未连边的点的数量

#include <cstdio>
#include <cstring>
#include <iostream>

using namespace std;
#define int long long 
const int N = 655;
const int Mod = 1e9 + 7;
int pre[N],to[N],f[N][N],g[N];
int n,k,ans,q;
inline int read() {
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9') {
        if(ch=='-')
            f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9') {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}
int c(int x,int y) {
	return (pre[y]-pre[x-1]+Mod)%Mod;
}
signed main () {
	n = read();q = read();
	n = n << 1;
	g[0] = 1;
	for(int i = 2;i <= n;i += 2) g[i] = g[i-2] * (i-1) % Mod;
	for(int i = 1;i <= n;i ++) pre[i] = i; 
	for(int i = 1;i <= q;i++) {
		int x,y;
		x = read();y = read();
		to[x] = y;to[y] = x;
		for(int j = x;j <= n;j++) --pre[j];
		for(int j = y;j <= n;j++) --pre[j];
	}
	for(int i = 1;i <= n;i++) {
		for(int j = i + 1;j <= n;j += 2) {
			int ok = 0,len = j-i+1;
			for(int k = i;k <= j;k++) {
				if(to[k]) {
					if(to[k]<i||to[k]>j) {
						ok = 1;
						break;
					}
				}	
			}
			if(ok) continue;
			f[i][j] = g[c(i,j)] % Mod;
			for(int k = i;k <= j-1;k++) {
				f[i][j] = (f[i][j] - f[i][k] * g[c(k+1,j)]%Mod)%Mod;
				f[i][j] = (f[i][j] + Mod)%Mod;
			}
			ans = (ans + f[i][j] * g[n-2*q-c(i,j)]%Mod)%Mod;
		}
	}
	printf("%lld\n",ans);
	return 0;
}
posted @ 2020-12-14 10:34  ctt2006  阅读(180)  评论(0)    收藏  举报