首先可以发现的是,因为两条线段不能在除了端点处相交,因此整个多边形最终一定会被连接成最上方由若干条线段在端点处相交连成,每条线段下方又是一个子结构。
因此你会发现,每个合法的状态都能被分成两个合法的子结构,因此可以考虑使用区间 (dp) 来解决这个问题。
首先,我们简单地考虑令 (dp_{i, j}) 表示只使用 (i, j) 这个区间之间的边将 (i, j) 这个区间联通的方案。
- 直接在 (i, j) 之间连边。
那么枚举每个 (k) 做为中间的断点,那么就有转移:
[dp_{i, j} = sumlimits_{k = i} ^ {j - 1} dp_{i, k} imes dp_{k + 1, j}(a_{i, j} = 1)
]
- 不直接在 (i, j) 之间连边。
同样考虑枚举中间的端点 (k),那么一个合法的方案就会被描述为用 (i sim k) 之间的边联通 (i sim k) 另一边类似,于是有转移:
[dp_{i, j} = sumlimits_{k = i + 1} ^ {j - 1} dp_{i, k} imes dp_{k, j}
]
但其实你会发现,同一个断点的方案会被所有最外层线段交点的位置计算一次,那么一个最简单的想法就是让这个方案只被计算一次。
于此同时你可以发现我们目前只会计算直接连接两个端点的方案,因此让这个方案在左上边第一个线段交点的位置计算一次是一个不赖的选择。
因此我们需要改写一下 (dp) 的状态,令 (dp_{i, j, 0 / 1}) 分别表示直接连接 (i, j) 和不直接连接 (i, j) 的方案。
那么这里的转移就应该变为:
[dp_{i, j, 1} = sumlimits_{k = i + 1} ^ {j - 1} (dp_{i, k, 0} + dp_{i, k, 1}) imes dp_{k, j, 0}
]
对应地改写第一条转移方程即可。
复杂度 (O(n ^ 3))。
#include <bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for (int i = l; i <= r; ++i)
const int N = 500 + 5;
const int Mod = 1e9 + 7;
int n, a[N][N], f[N][N], dp[N][N][2];
int read() {
char c; int x = 0, f = 1;
c = getchar();
while (c > '9' || c < '0') { if(c == '-') f = -1; c = getchar();}
while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
int Inc(int a, int b) { return (a += b) >= Mod ? a - Mod : a;}
int Mul(int a, int b) { return 1ll * a * b % Mod;}
int main() {
n = read();
rep(i, 1, n) rep(j, 1, n) a[i][j] = read();
rep(i, 1, n) dp[i][i][1] = f[i][i] = 1;
rep(i, 1, n) dp[i][i + 1][0] = f[i][i + 1] = a[i][i + 1];
rep(len, 3, n) rep(i, 1, n) {
int j = i + len - 1; if(j > n) break;
rep(k, i, j - 1) if(a[i][j]) dp[i][j][0] = Inc(dp[i][j][0], Mul(f[i][k], f[k + 1][j]));
rep(k, i + 1, j - 1) dp[i][j][1] = Inc(dp[i][j][1], Mul(f[i][k], dp[k][j][0]));
f[i][j] = Inc(dp[i][j][0], dp[i][j][1]);
}
printf("%d", f[1][n]);
return 0;
}
值得一提的是,(dp) 的转移一定要想办法将这个问题拆解成已经解决的子问题的结构,例如区间 (dp) 中就一定要能描述为左右两个合法区间的结合或其他的意义。