【2019 南昌邀请赛 Match Stick Game】 火柴dp

https://nanti.jisuanke.com/t/38223

题意

给出一个用火柴组成的表达式,由k个项和k-1个运算符组成。要求重组这些火柴,使得重组后的表达式结果最大。
重组的表达式要满足下面条件。

  1. 每项的位数必须相同,不能有前导零。
  2. 项数必须相同。

题解

预处理出
f[i][j][0/1]:代表i位数用j根火柴组成的最大/最小数。
mx[k]: k根火柴可以组成的最大值。
f [ i ] [ j ] [ 0 ] = m a x { f [ i − 1 ] [ j − k ] [ 0 ] ∗ 10 + m x [ k ] } f[i][j][0] = max\{f[i-1][j-k][0]*10+mx[k]\} f[i][j][0]=max{f[i1][jk][0]10+mx[k]}
mn[k]: k根火柴可以组成的最小值。
f [ i ] [ j ] [ 1 ] = m i n { f [ i − 1 ] [ j − k ] [ 1 ] ∗ 10 + m n [ k ] } f[i][j][1] = min\{f[i-1][j-k][1]*10+mn[k]\} f[i][j][1]=min{f[i1][jk][1]10+mn[k]}

dp[i][j][0/1]: 前i项还剩j根火柴,前面运算符 是+/- 得出的最大值。
0: 代表前面是+,1: 代表前面是-,v[i]:第i项是几位数。
k: 该项用k根火柴。
d p [ i ] [ j ] [ 0 ] = m a x { d p [ i − 1 ] [ j − k − 2 ] [ 0 ] + f [ v [ i ] ] [ j − k − 2 ] [ 0 ] , d p [ i − 1 ] [ j − k − 1 ] [ 1 ] + f [ v [ i ] ] [ j − k − 1 ] [ 0 ] } dp[i][j][0] = max\{dp[i-1][j-k-2][0]+f[v[i]][j-k-2][0], dp[i-1][j-k-1][1]+f[v[i]][j-k-1][0]\} dp[i][j][0]=max{dp[i1][jk2][0]+f[v[i]][jk2][0],dp[i1][jk1][1]+f[v[i]][jk1][0]}
d p [ i ] [ j ] [ 1 ] = m a x { d p [ i − 1 ] [ j − k − 1 ] [ 0 ] + f [ v [ i ] ] [ j − k − 1 ] [ 1 ] , d p [ i − 1 ] [ j − k − 1 ] [ 1 ] + f [ v [ i ] ] [ j − k − 1 ] [ 1 ] } dp[i][j][1] = max\{ dp[i-1][j-k-1][0]+f[v[i]][j-k-1][1], dp[i-1][j-k-1][1]+f[v[i]][j-k-1][1]\} dp[i][j][1]=max{dp[i1][jk1][0]+f[v[i]][jk1][1],dp[i1][jk1][1]+f[v[i]][jk1][1]}

意思就是,如果第i项前面是+号,那么就要让该项加上尽量大的值,否则就加上尽量小的值。所以要预处理出f数组。

要特别注意初始化!!!

代码

#include <bits/stdc++.h>
using namespace std;
#define FOR0(a,b) for(int i = a; i < b; ++i)
#define FORE(a,b) for(int i = a; i <= b; ++i)
typedef long long ll;
typedef pair<int,int> pii;
const int maxn = 200;
const int mx[] = {0,0,1,7,4,5,9,8}; // i根火柴最大数是多少 
const int mn[] = {0,0,1,7,4,2,0,8};
int tot, cnt;
char s[maxn];
ll f[maxn][705][2]; // 第i个数还剩j根火柴的 最大/最小 数
ll dp[maxn][705][2]; // 前i个数字 还剩 j 根火柴 组成的式子最大是多少
void init() {
	for(int i = 1; i <= 10; ++i)
		for(int j = 0; j <= 700; ++j)
			f[i][j][0] = -1e14, f[i][j][1] = 1e14;
	f[0][0][0] = f[0][0][1] = 0;
	for(int i = 1; i <= 10; ++i) {
		for(int j = 2; j <= 100; ++j) {
			for(int k = 2; k <= 7; ++k) {
				if(j-k >= 0) {
					f[i][j][0] = max(f[i][j][0], f[i-1][j-k][0]*10+mx[k]);
					f[i][j][1] = min(f[i][j][1], f[i-1][j-k][1]*10+mn[k]);
				}
			}
			// cout << i <<" " << j << " " << f[i][j][1] << endl;
		}
	}
}

int main() {
	int T;
	scanf("%d", &T);
	init();
	while(T--) {
		int n;
		scanf("%d", &n);
		scanf("%s", s);
		tot = cnt = 0;
		int len = strlen(s);
		vector<int> v;
		int t = 0;
		for(int i = 0; i < len; ++i) {
			if(s[i] >= '0' && s[i] <= '9') {
				t++; 
			}
			if(s[i] == '1') tot += 2;
			else if(s[i] == '2') tot += 5;
			else if(s[i] == '3') tot += 5;
			else if(s[i] == '4') tot += 4;
			else if(s[i] == '5') tot += 5;
			else if(s[i] == '6') tot += 6;
			else if(s[i] == '7') tot += 3;
			else if(s[i] == '8') tot += 7;
			else if(s[i] == '9') tot += 6;
			else if(s[i] == '0') tot += 6;
			else if(s[i] == '+') {tot += 2; cnt++; v.push_back(t); t = 0;}
			else if(s[i] == '-') {tot += 1; cnt++; v.push_back(t); t = 0;}
		}
		v.push_back(t);

		for(int i = 0; i < v.size(); ++i) for(int j = 0; j <= tot; ++j) dp[i][j][0] = dp[i][j][1] = -1e14;
		for(int j = 2; j <= tot; ++j) {
			dp[0][j][1] = dp[0][j][0] = f[v[0]][j][0];
			// cout << 0 <<" " << j << " " << dp[0][j][0] << endl;
		}
		for(int i = 1; i < v.size(); ++i) {
			for(int j = 0; j <= tot; ++j) {
				for(int k = 0; k <= j; ++k) {
					if(k+2 <= j) {
						dp[i][j][0] = max(dp[i][j][0], dp[i-1][j-k-2][0]+f[v[i]][k][0]);
						dp[i][j][0] = max(dp[i][j][0], dp[i-1][j-k-2][1]+f[v[i]][k][0]);
						// cout << dp[i-1][j-k-2][0]<<" " << dp[i-1][j-k-2][1] << " "<< f[v[i]][k][0] << endl;
						// cout << dp[i][j][0] << endl;	
					}

					if(k+1 <= j) {
						dp[i][j][1] = max(dp[i][j][1], dp[i-1][j-k-1][0]-f[v[i]][k][1]);
						dp[i][j][1] = max(dp[i][j][1], dp[i-1][j-k-1][1]-f[v[i]][k][1]);
					}
				}
				// cout << i << " " << j <<" " << dp[i][j][1] << endl;
			}
		}
		ll ans = -1e14;
		// cout << cnt << endl;
		for(int i = 0; i <= tot; ++i) {
			ans = max(ans, max(dp[cnt][i][0],dp[cnt][i][1]));
		}
		printf("%d\n", ans);
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值