hdu5396 2015多校第九场 区间dp

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5396


题意:给你n个数和n-1个操作符(2<=n<=100),问你不断地每次给操作符的左右相邻的两个数加括号,直到最后只剩一个数为止,问你不同的方案的所有最终剩下一个数的和是多少。在一次取操作符时只要操作符的位置不相同就算一种方案。如果有n个操作符的话肯定有n!种方案。


思路:看到n比较小。。。不是搜索就是区间dp。。。显然,搜索是n!种方案,算到量子计算机被研制出来都算不出来。。。那肯定区间dp了。。既然是dp,那么状态方程和转移明了了,这道题就可解了。。。

设dp[i][j]为第i个数到第j个数的所有方案的和,那么假设我们已经枚举到区间i,j(i < j),对于加法和减法这两种情况是类似的,我们考虑第k个操作符(i<=k<j,假设k这个操作符是最后一次做的,两边的操作符都已经完成了),有

dp[i][j] += (dp[i][k] * A[j - (k + 1)] % mod + dp[k + 1][j] * A[k - i] % mod) % mod * C[j - i - 1][k - i] % mod

其中A数组储存阶乘值,C数组储存组合值。

为什么要乘以阶乘?对于左边的i到k来说,假设右边有n个符号,那么右边肯定有n!种方案,对于每个方案都要加上左边的总和,所以要乘以一个阶乘。同理右边。。。。

为什么要乘以一个组合数?因为第k个操作符是最后取的,那么假设左边有n个操作符,右边有m个操作符,这么操作符取的顺序不同,最后的方案数也是不同的,所以我们把n+m个看作是取的顺序的位置,C[n + m][n]就相当与左边那些操作符放在哪些顺序的位置上。。。。


乘法的转移方程稍微有些不同,

dp[i][j] += (dp[i][k] * dp[k + 1][j] % mod) * C[j - i - 1][k - i] % mod就行了



代码:

#include 
   
   
    
    
#define mem(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
const int maxn = 150;
const ll mod = 1e9 + 7;

string op;
ll ori[maxn];
ll dp[maxn][maxn];
string s;
ll A[maxn];
ll C[maxn][maxn];
int n;

void pre()
{
	C[1][0] = C[1][1] = 1LL;
	C[0][0] = C[0][1] = 1LL;
	A[0] = A[1] = 1;
	for(int i = 1;i <= 102;i++)
	{
		C[i][0] = C[i][i] = 1LL;
	}
	for(ll i = 2;i <= 102;i++)
	{
		A[i] = A[i - 1] * i % mod;
		for(int j = 1;j < i;j++)
		C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
	}
}

int main()
{
	pre();
	while(~scanf("%d",&n))
	{
		mem(dp,0);
		for(int i = 1;i <= n;i++){
			scanf("%lld",ori + i);dp[i][i] = ori[i];
		}
		cin>>s;
		op = "(";
		op += s;
		for(int i = n;i >= 1;i--)
		{
			for(int j = i + 1;j <= n;j++)
			{
				for(int k = i;k < j;k++)
				{
					if(op[k] == '+'){
						dp[i][j] += (dp[i][k] * A[j - (k + 1)] % mod + dp[k + 1][j] * A[k - i] % mod) % mod * C[j - i - 1][k - i] % mod;
						dp[i][j] %= mod;
					}else if(op[k] == '-'){
						dp[i][j] += (dp[i][k] * A[j - (k + 1)] - dp[k + 1][j] * A[k - i]) % mod * C[j - i - 1][k - i] % mod;
						dp[i][j] %= mod;
						dp[i][j] %= mod;
					}else if(op[k] == '*'){
						dp[i][j] += (dp[i][k] * dp[k + 1][j] % mod) * C[j - i - 1][k - i] % mod;
						dp[i][j] += mod;
						dp[i][j] %= mod;
					}
				}
			}
		}
		while(dp[1][n] < 0)//可能小于0的。。不加会wa
			dp[1][n] += mod;
		printf("%lld\n",dp[1][n] % mod);
	}
}
   
   

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值