poj2955
题意:
求最长的”正则括号“的长度,正则括号定义如下:
1.空串是正则括号
2.如果s是正则括号,则(s)和[s]也是
3.如果a和b都是正则括号,则ab也是
4.第四条没看懂,(正则序列可以是原来序列的子序列(我自己胡乱说的))
分析:
区间dp,区间长度从1枚举到最长.(dp[i][j]表示i~j这个串中正则序列最长的长度)
如果区间端点处两个字符匹配,那么dp[i][j] = dp[i+1][j-1] + 2
,然后再更新这个区间的值,枚举这个区间的间断点k,dp[i][j]的值与左半区间值加右半区间的值取最大值。
AC代码:
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
string s;
int dp[105][105];
int len;
bool match(int i, int j)
{
if(s[i] == '(' && s[j] == ')') return true;
if(s[i] == '[' && s[j] == ']') return true;
return false;
}
int main()
{
while(cin >> s && s[0] != 'e')
{
memset(dp, 0, sizeof(dp));
len = s.size();
for(int d = 1; d <= len; ++d)
{
for(int i = 0; i < len - 1 && i + d - 1 < len; ++i)
{
int j = i + d - 1;
if(match(i, j)) dp[i][j] = dp[i + 1][j - 1] + 2;
for(int k = i; k < j; ++k)
{
dp[i][j] = max(dp[i][j], dp[i][k] + dp[k + 1][j]);
}
}
}
cout << dp[0][len - 1] << '\n';
}
return 0;
}
石子合并系列
三类
(1)有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动任意的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成
这种甚至可以不用dp,直接用堆贪心即可。
(2)有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动相邻的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(或最大)。
(3)把问题(2)中排成一堆改成排成一个环
石子合并(直线版)
分析:
dp[i][j]表示把第i~j堆石子合并的最小花费
sum数组是前缀和
要求的是dp[i][j]的最小值,我们可以枚举分界点k,即前n-2次合并把石子变成了1~k为一堆, k +1 ~ n为一堆,那么自然dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + sum[j] - sum[i - 1])
然后需要注意的就是求dp[i][j]的时候要保证dp[i][k]和dp[k+1][j]已经求出来了,那么就需要注意一下循环的顺序。
AC代码:
#include <bits/stdc++.h>
using namespace std;
int n;
int ar[1005], dp[1005][1005];
int sum[1005];
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
{
scanf("%d", &ar[i]);
sum[i] = sum[i - 1] + ar[i];
}
memset(dp, 0x3f, sizeof(dp));
for(int i = 1; i <= n; ++i) dp[i][i] = 0;
//枚举顺序是区间后端点从小到大,区间长度从小到大
for(int j = 1; j <= n; ++j)
{
for(int i = j - 1; i >= 1; --i)
{
for(int k = i; k < j; ++k)
{
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + sum[j] - sum[i - 1]);
}
}
}
printf("%d\n", dp[1][n]);
return 0;
}
石子合并(环)
分析:
大体思路跟上面那个题一样。只是需要对环处理,处理方法以下面为例
对于样例
4
4 5 9 4
变成4 5 9 4 4 5 9 4(变成原来的两倍),然后答案从dp[1][n],dp[2][n + 1], dp[3][n + 2],…, dp[n][2 * n - 1]中取,实质是枚举合并n-2次后两堆石子的状态,即在哪两个点处断成两个链
AC代码:
#include <bits/stdc++.h>
using namespace std;
int n;
int ar[1005];
int f1[1005][1005], f2[1005][1005];
int sum[1005];
int mi = 0x3f3f3f3f, mx = -1;
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
{
scanf("%d", &ar[i]);
sum[i] = sum[i - 1] + ar[i];
}
for(int i = n + 1; i <= 2 * n; ++i)
{
ar[i] = ar[i - n];
sum[i] = sum[i - 1] + ar[i];
}
memset(f1, 0x3f, sizeof(f1));
for(int i = 1; i <= 2 * n; ++i) f1[i][i] = 0;
for(int j = 1; j <= 2 * n; ++j)
{
for(int i = j - 1; i >= 1; --i)
{
for(int k = i; k < j; ++k)
{
f1[i][j] = min(f1[i][j], f1[i][k] + f1[k + 1][j] + sum[j] - sum[i - 1]);
f2[i][j] = max(f2[i][j], f2[i][k] + f2[k + 1][j] + sum[j] - sum[i - 1]);
}
}
}
for(int i = 1; i <= n; ++i)
{
mx = max(mx, f2[i][i + n - 1]);
mi = min(mi, f1[i][i + n - 1]);
}
cout << mi << '\n' << mx << '\n';
return 0;
}
POJ2287 田忌赛马
题意:
给你n匹田忌的马的速度,n批齐王马的速度,傻逼齐王只会按照马的速度排序,显出最快的马。一共会比n次,输的一方需要给赢的一方200元,输出田忌最多挣多少钱。
分析:
首先很容易想到贪心,先对他俩的马排序,然后遍历齐王的每一匹马,如果能打过就打,打不过就那最笨的马跟他打。
但是这样存在一个问题,就是如果平局(两人最牛逼的一样厉害)这是平局会消耗田忌一匹最牛逼的马,然后不扣钱,选一个最笨的马去打会保留这匹牛逼的马,但是会扣200块,这两种做法在不同的情况下效果不同。
对于下面的例子
• 例子1:
• 田忌 :1 2 3
• 齐王 :1 2 3
(应该先输)
• 例子2:
• 田忌:2 3
• 齐王:1 3
(应该打平局)
再仔细想想会发现,田忌每次排的马不是最牛逼的就是最拉跨的,所以我们可以动态规划。
用f[i][j]表示田忌区间[i~j]的马比完之后,田忌挣的钱,则状态转移方程是
f[i][j] = max(f[i+1][j] + cost(i,k), f[i][j-1] + cost(j,k));
//k表示齐王当前出的马,cost(i,k)是田忌第i匹马与齐王第k匹马相比的结果。
要搞明白田忌的每个区间对应的是齐王的那一匹马区间[i~j]说明已经有n - (j - i + 1)只马比完了,那么对应的就是第n - (j - i + 1) + 1只马
然后是边界情况f[i][i],即田忌只剩下第i匹马了,齐王只剩下最笨的那匹马了,它的值应该等于cost(i,n)
这个题还有一个需要理解的是他们比赛是从n匹马比到1匹马,而我们推的时候是先得到齐王只有一匹马(最笨的那一匹)的时候,田忌有一匹马(不知道是那匹)的时候他们挣钱的情况,然后由这个边界条件推回去,正好跟比赛的顺序相反。
AC代码:
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int n;
int ar[1005], br[1005];
int f[1005][1005];
bool cmp(int a, int b)
{
return a > b;
}
int cost(int i, int j)
{
if(ar[i] > br[j]) return 200;
else if(ar[i] == br[j]) return 0;
else return -200;
}
int main()
{
while(scanf("%d", &n) != EOF && n)
{
memset(f, 0, sizeof(f));
for(int i = 1; i <= n; ++i) scanf("%d", &ar[i]);
for(int i = 1; i <= n; ++i) scanf("%d", &br[i]);
sort(ar + 1, ar + 1 + n, cmp);
sort(br + 1, br + 1 + n, cmp);
for(int i = 1; i <= n; ++i) f[i][i] = cost(i, n);
for(int j = 1; j <= n; ++j)
{
for(int i = j - 1; i >= 1; --i)
{
int k = n - (j - i + 1) + 1;
f[i][j] = max(f[i + 1][j] + cost(i, k), f[i][j - 1] + cost(j, k));
}
}
printf("%d\n", f[1][n]);
}
return 0;
}