P4580 路径
forgotmyhandle · · 题解
传送门
分析
可以考虑 dp。先朴素地定义
发现如果要排除掉前导
于是有最终的状态:
接下来考虑转移(前方有大分讨,请注意):
对于
-
若
i 结点上是数字:- 若
v 结点上是非0 数字,则将dp[v][j - 1][k][0] + dp[v][j - 1][k][1] 加到dp[i][j][k][0] ,因为这时我们不关心v 上的数是否作为首位; - 若
v 结点上是0 ,则将dp[v][j - 1][k][0] 加到dp[i][j][k][0] ,因为这时若v 上的0 作首位,后面就不能接数字; - 若
v 结点上是运算符或前括号,则将dp[v][j - 1][k][2] 加到dp[i][j][k][1] ,因为数字前面一定可以接这两者; - 由于后括号之后不接数字,所以
v 上是后括号时不转移。
注意,当
v 上是数字时,当前位的数字就不作为首位。当v 上不是数字,则当前位的数字就必然作为首位。 - 若
- 若
i 结点上是运算符:- 若
v 上为数字,则将dp[v][j - 1][k][0] + dp[v][j - 1][k][1] 加过来,因为运算符前一定可以接数字; - 若
v 是前括号,则当且仅当i 上为减号时可以将dp[v][j - 1][k][2] 加过来,因为这时减号作负号用,而其他运算符均没有该用法; - 若
v 是后括号,则将dp[v][j - 1][k][2] 加过来,因为后括号后一定可以接运算符; - 由于任意两运算符不能相连,于是当
v 是运算符时不转移。
- 若
-
若
i 上是前括号:- 当且仅当
v 上是运算符或前括号时可以将dp[v][j - 1][k - 1][2] 加过来,因为只有这两者后才可以接前括号。(没说乘号可以省就不能省(确信))
注意,由于多了一个未匹配的前括号,所以第三维从
k - 1 转移。 - 当且仅当
-
若
i 上是后括号:- 若
v 上是数字,则将dp[v][j - 1][k + 1][0] + dp[v][j - 1][k + 1][1] 加过来; - 若
v 上是后括号,则将dp[v][j - 1][k + 1][2] 加过来; - 由于运算符后不能直接加后括号,括号内又不能为空,于是
v 为运算符或前括号时不转移。
注意,由于这个后括号匹配了一个前括号,所以第三维从
k + 1 转移。 - 若
赋初值的时候注意,当且仅当这个结点上是数字或前括号或减号时才能有初值。
统计答案时注意,当且仅当最后一位是后括号或数字时才可以加入答案。
代码
#include <iostream>
#define int long long
using namespace std;
const int p = 1000000007;
int head[200005], nxt[400005], to[400005], ecnt;
void addE(int u, int v) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt; }
int n, m, K;
int dp[25][35][35][3];
// currently at point i, has passed j vertices, have k front bracket left to match
// l = (0 : this digit is not the beginning, 1 : is beginning, 2 : not a digit)
bool issym(char x) { return (x == '+' || x == '-' || x == '*' || x == '/'); }
bool isbrc(char x) { return (x == '(' || x == ')'); }
inline void add(int& x, int y) { x = (x + y) % p; }
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> m >> K;
string str;
cin >> str;
str = ' ' + str;
for (int i = 1, u, v; i <= m; i++) {
cin >> u >> v;
addE(u, v);
addE(v, u);
}
for (int i = 1; i <= n; i++) {
if (isdigit(str[i]))
dp[i][1][0][1] = 1;
else if (str[i] == '(')
dp[i][1][1][2] = 1;
else if (str[i] == '-')
dp[i][1][0][2] = 1;
}
for (int j = 2; j <= K; j++) {
for (int i = 1; i <= n; i++) {
for (int k = (str[i] == '('); k <= K; k++) {
for (int l = head[i]; l; l = nxt[l]) {
int v = to[l];
if (isdigit(str[i])) {
if (isdigit(str[v]))
add(dp[i][j][k][0], dp[v][j - 1][k][0] + (str[v] == '0' ? 0 : dp[v][j - 1][k][1]));
else if (issym(str[v]) || str[v] == '(')
add(dp[i][j][k][1], dp[v][j - 1][k][2]);
} else if (issym(str[i])) {
if (isdigit(str[v]))
add(dp[i][j][k][2], dp[v][j - 1][k][0] + dp[v][j - 1][k][1]);
else if (str[v] == '(')
str[i] == '-' ? add(dp[i][j][k][2], dp[v][j - 1][k][2]) : void();
else if (str[v] == ')')
add(dp[i][j][k][2], dp[v][j - 1][k][2]);
} else if (str[i] == '(') {
if (issym(str[v]) || str[v] == '(')
add(dp[i][j][k][2], dp[v][j - 1][k - 1][2]);
} else {
if (isdigit(str[v]))
add(dp[i][j][k][2], dp[v][j - 1][k + 1][0] + dp[v][j - 1][k + 1][1]);
else if (str[v] == ')')
add(dp[i][j][k][2], dp[v][j - 1][k + 1][2]);
}
}
}
}
}
int ans = 0;
for (int i = 1; i <= n; i++) {
if (isdigit(str[i]))
add(ans, dp[i][K][0][0] + dp[i][K][0][1]);
else if (str[i] == ')')
add(ans, dp[i][K][0][2]);
}
cout << ans;
return 0;
}
关于什么情况下要赋初值:
这里的 dp 值的意义实际上是当前合法的方案数 加上 当前非法但到后面可能变得合法的方案数。显然只有一个减号或前括号都是可能变得合法的,于是有初值。又显然只有一个后括号或别的运算符都是永远也不可能变得合法的,于是就没有初值。
关于统计答案:
显然这里的非法情况只有两种;运算符缺第二个参数 和 前括号没匹配完。于是只要最后一位是数字或后括号,再加上只统计第三维是