区间dp学习、

参考学习:传送门

POJ 2955

题意:给出一个由'(', ')', '[', ']'组成的字符串,现在让你找出一个子序列出来使得子序列是合法括号匹配,求出子序列的最大括号匹配。

思路:dp[i][j]代表区间[i, j] 的最大匹配,转移的话看代码吧

#include <cstdio>
#include <cstring>
#include <map>
#include <algorithm>
#include <iostream>
#include <vector>
#include <stack>

using namespace std;
#define LL long long
#define pb push_back
#define mk make_pair
#define pill pair<int, int>
#define mst(a, b)	memset(a, b, sizeof a)
#define REP(i, x, n)	for(int i = x; i <= n; ++i)
const int MOD = 1e9 + 7;
const int qq = 105;
char st[qq];
int dp[qq][qq];

int main(){
	while(scanf("%s", st) != EOF) {
		mst(dp, 0);
		if(st[0] == 'e')	break;
		int len = strlen(st);
		for(int i = 1; i < len; ++i){
			for(int j = 0, k = i; k < len; ++j, ++k){
				if((st[j] == '(' && st[k] == ')') || (st[j] == '[' && st[k] == ']')){
					dp[j][k] = dp[j + 1][k - 1] + 2;
				}
				for(int x = j; x < k; ++x) {
					dp[j][k] = max(dp[j][k], dp[j][x] + dp[x + 1][k]);
				}
			}
		}
		printf("%d\n", dp[0][len - 1]);
	}
	return 0;
}



HDU2476

题意:给出两个串A和B,可以对A串进行操作,对A的某个区间填充一个字符,问最小操作次数使A变成B串

思路:参考了网上的博客区间dp,基本思路是求空串变成B串要求的dp[i][j],然后考虑A串

#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <sstream>
#include <map>
#include <set>
#include <vector>
#include <utility>
#include <queue>
#include <stack>

using namespace std;
#define LL long long
#define pb push_back
#define mk make_pair
#define pill pair<int, int>
#define mst(a, b)	memset(a, b, sizeof a)
#define REP(i, x, n)	for(int i = x; i <= n; ++i)
const int MOD = 1e9 + 7;
const int qq = 105;
int dp[qq][qq];
char str1[qq], str2[qq];
int ans[qq];

int main(){
	while(scanf("%s%s", str1, str2) == 2) {
		mst(dp, 0);
		int n = strlen(str1);
		for(int i = 0; i < n; ++i) {
			for(int j = i; j < n; ++j) {
				dp[i][j] = j - i + 1;
			}
		}
		for(int i = n - 2; i >= 0; --i) {
			for(int j = i + 1; j < n; ++j) {
				dp[i][j] = dp[i + 1][j] + 1;
				for(int k = i + 1; k <= j; ++k) {
					if(str2[i] == str2[k]) {
						dp[i][j] = min(dp[i][j], dp[i + 1][k - 1] + dp[k][j]);
					}
				}
			}
		}
		for(int i = 0; i < n; ++i) {
			ans[i] = dp[0][i];
			if(str1[i] == str2[i]) {
				if(i == 0)	ans[i] = 0;
				else	ans[i] = ans[i - 1];
			}
			for(int k = 0; k < i; ++k) {
				ans[i] = min(ans[i], ans[k] + dp[k + 1][i]);
			}
		}
		printf("%d\n", ans[n - 1]);
	}
	return 0;
}


POJ1141

参考:传送门

这方法是真的巧妙,QAQ

#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <sstream>
#include <map>
#include <set>
#include <vector>
#include <utility>
#include <queue>
#include <stack>

using namespace std;
#define LL long long
#define pb push_back
#define mk make_pair
#define pill pair<int, int>
#define mst(a, b)	memset(a, b, sizeof a)
#define REP(i, x, n)	for(int i = x; i <= n; ++i)
const int MOD = 1e9 + 7;
const int qq = 100 + 10;
int d[qq][qq], c[qq][qq] = {-1};
int len;
string st;
void Dp() {
	int i, j, k, l;
	int minx;
	for(i = 0; i < len; ++i) d[i][i] = 1;
	for(l = 1; l < len; ++l) {
		for(i = 0; i + l < len; ++i) {
			j = i + l;
			minx = d[i][i] + d[i + 1][j];
			c[i][j] = i;
			for(k = i + 1; k < j; ++k) {
				if(d[i][k] + d[k + 1][j] < minx) {
					minx = d[i][k] + d[k + 1][j];
					c[i][j] = k;
				}
			}
			d[i][j] = minx;
			if((st[i] == '(' && st[j] == ')') || (st[i] == '[' && st[j] == ']')) {
				if(d[i + 1][j - 1] < minx) {
					d[i][j] = d[i + 1][j - 1];
					c[i][j] = -1;
				}
			}
		}
	}
}

void Print(int i, int j) {
	if(i > j)	return;
	if(i == j) {
		if(st[i] == '(' || st[i] == ')')	cout << "()";
		else	cout << "[]";
	}else {
		if(c[i][j] >= 0) {
			Print(i, c[i][j]);
			Print(c[i][j] + 1, j);
		}else {
			if(st[i] == '(') {
				cout << "(";
				Print(i + 1, j - 1);
				cout << ")";
			} else {
				cout << "[";
				Print(i + 1, j - 1);
				cout << "]";
			}
		}
	}
}

int main(){
	cin >> st;
	len = st.size();
	Dp();
	Print(0, len - 1);
	cout << endl;
	return 0;
}


codeforces 149D

题意:给出一个合法的括号序列,现在有三个条件
1.每一个括号要么没有颜色,要么红色,要么蓝色

2.每一对匹配的括号中有且只有一个有颜色

3.相邻的两个括号颜色不能相同。

现在问有多少种合法的填色方法


果然还是太弱了,完全没想到dfs- - 、

参考了某位聚聚的博客才理解这种做法。

dp[l][r][i][j] 代表区间[l, r] l涂i颜色,r涂j颜色的合法方式的种数

然后匹配区间进行递归,最后由小区间更新大区间。

#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <sstream>
#include <map>
#include <set>
#include <vector>
#include <utility>
#include <queue>
#include <stack>

using namespace std;
#define LL long long
#define pb push_back
#define mk make_pair
#define pill pair<int, int>
#define mst(a, b)	memset(a, b, sizeof a)
#define REP(i, x, n)	for(int i = x; i <= n; ++i)
const int MOD = 1e9 + 7;
const int qq = 705;
LL dp[qq][qq][3][3];
int match[qq];
int Stack[qq];
char st[qq];
void Dfs(int l, int r) {
	if(l + 1 == r) {
		dp[l][r][0][1] = dp[l][r][0][2] = dp[l][r][1][0] = dp[l][r][2][0] = 1;
		return;
	}
	if(match[l] == r) {
		Dfs(l + 1, r - 1);
		for(int i = 0; i < 3; ++i) {
			for(int j = 0; j < 3; ++j) {
				if(j != 1)	dp[l][r][0][1] = (dp[l][r][0][1] + dp[l + 1][r - 1][i][j]) % MOD;
				if(j != 2)	dp[l][r][0][2] = (dp[l][r][0][2] + dp[l + 1][r - 1][i][j]) % MOD;
				if(i != 1)	dp[l][r][1][0] = (dp[l][r][1][0] + dp[l + 1][r - 1][i][j]) % MOD;
				if(i != 2)	dp[l][r][2][0] = (dp[l][r][2][0] + dp[l + 1][r - 1][i][j]) % MOD;
			}
		}
	} else {
		Dfs(l, match[l]);
		Dfs(match[l] + 1, r);
		for(int i = 0; i < 3; ++i) {
			for(int j = 0; j < 3; ++j) {
				for(int k = 0; k < 3; ++k) {
					for(int h = 0; h < 3; ++h) {
						if(j && j == k)	continue;
						dp[l][r][i][h] = (dp[l][r][i][h] + (dp[l][match[l]][i][j] * dp[match[l] + 1][r][k][h] % MOD + MOD) % MOD) % MOD;
					}
				}
			}
		}
	}
}

int main(){
	scanf("%s", st + 1);
	int len = strlen(st + 1);
	int top = 0;
	for(int i = 1; i <= len; ++i) {
		if(st[i] == '(') {
			Stack[top++] = i;
		} else {
			match[Stack[--top]] = i;
		}
	}
	mst(dp, 0);
	Dfs(1, len);
	LL ans = 0;
	for(int i = 0; i < 3; ++i) {
		for(int j = 0; j < 3; ++j) {
			ans = ((ans + dp[1][len][i][j]) % MOD + MOD) % MOD;
		}
	}
	printf("%lld\n", ans);
	return 0;
}



HDU 4283

题意:n个人排成一队,现在要让他们上场表演,每个人有一个Di值,如果第i个人第k个上场,那么这个人会产生怨气(k - 1) × Di,现在由一个小黑屋,是一个栈的结构,然后可以利用这个栈调整人的出场顺序,问产生的最少怨气值是多少

思路:dp[i][j],只考虑区间[i, j]人出场顺序时的最小怨气值,无论前面有多少人不管,i这个人可能第一个出场,也可能第j - i + 1个出场, 我们考虑i第k个出场,那么我们可以明确知道i后面k - 1个人一定是先上场的,然后i上场,剩余的再上场,就有子问题 dp[i + 1][i + k - 1] 这些人是前k - 1个上场,i第k个上场 产生的怨气(k - 1) * val[i], dp[i + k][j]这些人是后k个上场,但实际上dp[i + k][j]这个值只考虑了区间[i + k, j]的人上场顺序的最小值,但实际前面已经有k个人上场了,所以会产生(sum[j] - sum[i + k - 1]) * k的怨气值。

比如答案中第一个人是第k个上场,那么区间[2, k]的人是前k个上场的,这几个人上场顺序的最小值就是dp[2][k],也说明区间[k + 1, n]的人是再前k个人上场完再上场的但是dp[k + 1][n]仅仅只算了这(n - (k + 1) + 1)的上场顺序的最优怨气值,所以得加上k * (sum[n] - sum[k]).

#include <bits/stdc++.h>

using namespace std;
#define LL long long
#define mst(a, b)	memset(a, b, sizeof a)
#define REP(i, x, n)	for(int i = x; i <= n; ++i)
const int INF = 2e9;
const int qq = 2e5 + 10;
int dp[205][205];
int val[205], sum[205];

int main(){
	int n, t, Cas = 0;
	scanf("%d", &t);
	while(t--) {
		scanf("%d", &n);
		sum[0] = 0;
		for(int i = 1; i <= n; ++i) {
			scanf("%d", val + i);
			sum[i] = sum[i - 1] + val[i];
		}
		mst(dp, 0);
		for(int i = 1; i <= n; ++i)
			for(int j = i + 1; j <= n; ++j)
				dp[i][j] = INF;
		for(int l = 1; l <= n - 1; ++l) {
			for(int i = 1; i <= n - 1; ++i) {
				int j = i + l;
				for(int k = 1; k <= j - i + 1; ++k) {
					dp[i][j] = min(dp[i][j], dp[i + 1][i + k - 1] + dp[i + k][j] + k * (sum[j] - sum[i + k - 1]) + val[i] * (k - 1));
				}
			}
		}
		printf("Case #%d: %d\n", ++Cas, dp[1][n]);
	}
	return 0;
}



ZOJ 3541

题意:n个按钮,每个按钮有一个值Ti,代表在你按下这个按钮后Ti后这个按钮会弹上来,然后给出n个按钮的在坐标轴上的位置,问你能否有一种方案使得所有按钮都是down的状态。

思路:这题首先有一个结论,对于某个区间[i, j], 他一定是从左或者从右边开始按的,dp[i][j][0/1]代表区间[i, j] 从左向右按/右向左按所需的最短距离。

#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <sstream>
#include <map>
#include <set>
#include <vector>
#include <utility>
#include <queue>
#include <stack>

using namespace std;
#define LL long long
#define pb push_back
#define mk make_pair
#define pill pair<int, int>
#define mst(a, b)	memset(a, b, sizeof a)
#define REP(i, x, n)	for(int i = x; i <= n; ++i)
const int MOD = 1e9 + 7;
const int qq = 2e5 + 10;
const int INF = 1 << 30;
int dp[205][205][2], t[205], d[205], next[205][205][2];

int main(){
	int n;
	while(scanf("%d", &n) != EOF) {
		for(int i = 1; i <= n; ++i) {
			scanf("%d", t + i);
		}
		for(int i = 1; i <= n; ++i) {
			scanf("%d", d + i);
		}
		mst(dp, 0);
		for(int  l = 1; l < n; ++l) {
			for(int i = 1; i + l <= n; ++i) {
				int j = i + l;
				dp[i][j][0] = min(dp[i + 1][j][0] + d[i + 1] - d[i], dp[i + 1][j][1] + d[j] - d[i]);
				next[i][j][0] = (dp[i + 1][j][0] + d[i + 1] - d[i] >= dp[i + 1][j][1] + d[j] - d[i]);
				if(dp[i][j][0] >= t[i] || dp[i][j][0] > INF)
					dp[i][j][0] = INF;
				dp[i][j][1] = min(dp[i][j - 1][1] + d[j] - d[j - 1], dp[i][j - 1][0] + d[j] - d[i]);
				next[i][j][1] = (dp[i][j - 1][1] + d[j] - d[j - 1] <= dp[i][j - 1][0] + d[j] - d[i]);
				if(dp[i][j][1] >= t[j] || dp[i][j][1] > INF)
					dp[i][j][1] = INF;
			}
		}
/*		for(int l = 1; l < n; ++l) {
			for(int i = 1; i + l <= n; ++i) {
				int j = i + l;
				printf("%d %d %d %d\n", i, j, dp[i][j][0], dp[i][j][1]);
			}
		}*/
		int l, r, m;
		if(dp[1][n][0] < INF) {
			l = 2, r = n, m = next[1][n][0];
			printf("1");
		} else if(dp[1][n][1] < INF){
			l = 1, r = n - 1, m = next[1][n][1];
			printf("%d", n);
		} else {
			printf("Mission Impossible");
			l = 1, r = -1;
		}
		while(l <= r) {
			if(m == 0) {
				printf(" %d", l);
				m = next[l][r][m];
				l++;
			} else {
				printf(" %d", r);
				m = next[l][r][m];
				r--;
			}
		}
		puts("");
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值