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

还是场上考虑的太不周到了。

posted @ 2025-08-23 18:45  Lordreamland  阅读(9)  评论(0)    收藏  举报