参考学习:传送门
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;
}