[题解]CF888F Connecting Vertices
思路
注意到当连接 \((i,j)\) 边时,\([i,j]\) 区间中的点无法向区间外的点连边,这有着相当好的“独立性”,考虑区间 dp。
定义 \(dp_{i,j}\) 表示 \([i,j]\) 连成一个连通块时的方案数,考虑转移:
- 若 \(i,j\) 直接连边,枚举一个断点 \(k\) 表示我们需要将 \([i,k],(k,j]\) 两区间的点分别连成连通块,则有:\(dp_{i,j} \leftarrow \sum_{k = l}^{r - 1}dp_{i,k} \times dp_{k + 1,j}\)。
- 若 \(i,j\) 没有连边,枚举一个断点 \(k\) 表示我们需要连接 \((i,k),(k,j)\) 两条边,则有:\(dp_{i,j} \leftarrow \sum_{k = l}^{r}dp_{l,k} \times dp_{k,r}\)。
事实上,这个做法有一些瑕疵。当存在 \(i \to j \to k\) 时,断点分别在 \(j,k\) 时这种情况被计算了两次。对于这类问题,我们通常的套路是只记录第一个为断点/最后一个为断点的贡献,这里我们采用后者。
重新定义 \(dp_{i,j,0/1}\) 表示 \([i,j]\) 连成一个连通块时,\((i,j)\) 没有连边/有连边的方案数,考虑转移:
- \(dp_{i,j,0} \leftarrow \sum_{k = i}^{j}dp_{i,k,1} \times (dp_{k,j,0} + dp_{k,j,1})\),枚举断点 \(k\) 表示钦定 \(k\) 为 \(i\) 连出去某条链的末端。
- \(dp_{i,j,1} \leftarrow \sum_{k = i}^{j - 1}(dp_{i,k,0} + dp_{i,k,1}) \times (dp_{k + 1,j,0} + dp_{k + 1,j,1})\),这类情况并不会算重,转移和上面一样。
无法连边的限制显然是好处理的。
Code
#include <bits/stdc++.h>
#define re register
#define int long long
#define Add(a,b) (((a) + (b)) % mod)
#define Mul(a,b) ((a) * (b) % mod)
#define chAdd(a,b) (a = Add(a,b))
using namespace std;
const int N = 510;
const int mod = 1e9 + 7;
int n;
bool arr[N][N];
int dp[N][N][2];
inline int read(){
int r = 0,w = 1;
char c = getchar();
while (c < '0' || c > '9'){
if (c == '-') w = -1;
c = getchar();
}
while (c >= '0' && c <= '9'){
r = (r << 3) + (r << 1) + (c ^ 48);
c = getchar();
}
return r * w;
}
signed main(){
n = read();
for (re int i = 1;i <= n;i++){
for (re int j = 1;j <= n;j++) arr[i][j] = read();
}
for (re int i = 1;i <= n;i++) dp[i][i][0] = 1;
for (re int len = 2;len <= n;len++){
for (re int i = 1,j = len;j <= n;i++,j++){
for (re int k = i + 1;k <= j;k++) chAdd(dp[i][j][0],Mul(dp[i][k][1],Add(dp[k][j][0],dp[k][j][1])));
if (arr[i][j]){
for (re int k = i;k < j;k++) chAdd(dp[i][j][1],Mul(Add(dp[i][k][0],dp[i][k][1]),Add(dp[k + 1][j][0],dp[k + 1][j][1])));
}
}
} printf("%lld",Add(dp[1][n][0],dp[1][n][1]));
return 0;
}

浙公网安备 33010602011771号