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;
}

浙公网安备 33010602011771号