[HDU5396]Expression
传送门
很有意思的一个题。
一眼知道是区间dp,考虑记录和与方案数,然后段合并是好做的。下面是赛时代码。
ll calc(ll sum1, ll sum2, ll cas1, ll cas2, int x){
char nc = c[x];
if(nc == '+') return 1ll * (sum2 * cas1 % mod + cas2 * sum1 % mod + mod) % mod;
else if(nc == '-') return 1ll * (sum1 * cas2 % mod - cas1 * sum2 % mod + mod) % mod;
else return 1ll * (sum2 * sum1 + mod) % mod;
}
for(int i = 1; i <= n; i++) dp[i][i] = a[i], g[i][i] = 1ll;
for(int len = 2; len <= n; len++){
for(int l = 1; l + len - 1 <= n; l++){
int r = l + len - 1;
for(int k = l; k < r; k++){
(dp[l][r] += calc(dp[l][k], dp[k + 1][r], g[l][k], g[k + 1][r], k)) %= mod;
(g[l][r] += g[l][k] * g[k + 1][r] % mod) %= mod;
}
}
}
cout << dp[1][n];
然而发现甚至过不去样例。这是为什么呢?我不知道,所以赛时只有二十分。
原因是,在 calc 函数中,只计算了结果的每一种情况的答案和,而题目要求的是操作序列的每一种情况的答案和,换言之,在上面程序中我默认对于左右两边所有结果的任意一对组合,都是先通过某种方式计算得出左边的结果,再计算得到右边的结果,事实上,左右两边的计算是可以交叉进行的,只要保证两边的顺序分别按原来进行,无论怎么交叉,最终结果都是不变的。这意味这要额外乘上一个组合数的系数,也就是从区间所有符号中选左区间符号个数个符号的方案数,代表交叉的方案数。
ll calc(ll sum1, ll sum2, ll cas1, ll cas2, int x){
char nc = c[x];
if(nc == '+') return 1ll * (sum2 * cas1 % mod + cas2 * sum1 % mod + mod) % mod;
else if(nc == '-') return 1ll * (sum1 * cas2 % mod - cas1 * sum2 % mod + mod) % mod;
else return 1ll * (sum1 * sum2 + mod) % mod;
}
signed main(){
n = read();
C[0][0] = 1;
for(int i = 1; i <= n; i++){
C[i][0] = 1;
for(int j = 1; j <= i; j++){
C[i][j] = C[i - 1][j] + C[i - 1][j - 1];
C[i][j] %= mod;
}
}
for(int i = 1; i <= n; i++) a[i] = readll();
for(int i = 1; i < n; i++) cin >> c[i];
for(int i = 1; i <= n; i++) dp[i][i] = a[i], g[i][i] = 1ll;
for(int len = 2; len <= n; len++){
for(int l = 1; l + len - 1 <= n; l++){
int r = l + len - 1;
for(int k = l; k < r; k++){
(dp[l][r] += calc(dp[l][k], dp[k + 1][r], g[l][k], g[k + 1][r], k) * C[len - 2][k - l] % mod) %= mod;
(g[l][r] += C[len - 2][k - l] * g[l][k] % mod * g[k + 1][r] % mod) %= mod;
}
}
}
cout << dp[1][n];
return 0;
}
还是场上考虑的太不周到了。
$\color{blue} \mathcal {Lordreamland}$

浙公网安备 33010602011771号