基础的动态规划算法
poj3176 Cow Bowing
没什么好说的,就是数塔问题,从最后一层往上扫,f[i][j] = max(f[i + 1][j], f[i + 1][j + 1]) + a[i][j],当前状态只取决于底下两个的状态
poj2229 Sumset
求一个数分解成2的幂次之和有多少种情况。
这个问题还是需要想一想的,首先,我们应该意识到了,假如一个数是奇数,那么拆解后必含1(偶数加偶数还是偶数,2的幂次除了1都是偶数,如果没有1是不可能组成奇数的),所以当n奇数的时候,其实只要把n-1的所有情况全部加上1就行了,即f[n] = f[n - 1]。当n等于偶数时要复杂一些,但是仔细想想也能想到。当n为偶数时既可能含1也可能不含。如果有1的话,那么1一定是偶数个(奇数+奇数 = 偶数,偶数+奇数=奇数)。我们把含1和不含1的情况分开来考虑,假如含1,那么只要n-2的情况后面加上两个1即可,如果不含1呢?显然,只需要把n/2的情况每个元素都乘2就行了。举例来说 6 = (2 + 2 + 2) = (1 * 2 + 1 * 2 + 1 * 2)。所以当n为偶数的情况,f[n] = f[n/2] + f[n - 2]
代码
#include <iostream>
#include <cstdio>
#define ll long long
using namespace std;
const int maxn = 1000000 + 10;
const int mod = 1000000000;
int dp[maxn];
int main() {
int n;
cin >> n;
dp[1] = 1;
dp[2] = 2;
for(int i = 3; i <= n; i++) {
if(i % 2 == 1)
dp[i] = dp[i - 1];
else
dp[i] = (dp[i - 2] + dp[i / 2]) % mod;
}
cout << dp[n] << endl;
return 0;
}
poj3616 Milking Time
题意
有一头牛,每次挤奶后要休息r的时间,然后它的主人每次挤奶有一个个时间段,在这个时间段里可以让牛挤出一定的牛奶。这头牛想让自己挤出的牛奶最多,问它通过安排自己的挤奶时间能挤出的最大牛奶的量。
思路
实际上对于这头牛来说每一个时间段它都有两种选择,挤或不挤(当然也可能是还没休息好(好比技能还没冷却2333)不能挤)我们把每种状态都记录下来,对于当前的时间段,我们只要找到一个之前的时间段,距离当前时间段已经隔了时间r的并且挤的牛奶最多的状态,在此基础上再来挤。我们可以用一个数组来记录dp[i]表示到第i个时间段为止挤的最多的牛奶的量
转移状态
if(a[i].starting_hour >= a[j].ending_hour + r)
dp[i] = max(dp[i], dp[j] + a[i].efficiency)
代码
#include <iostream>
#include <algorithm>
using namespace std;
int const maxn = 1000 +10;
int dp[maxn];
struct Interval {
int starting_hour,ending_hour,efficiency;
}a[maxn];
bool cmp(Interval a, Interval b) {
if(a.starting_hour == b.starting_hour) {
return a.ending_hour < b.ending_hour;
}
return a.starting_hour < b.starting_hour;
}
int main() {
int n,m,r;
cin >> n >> m >> r;
for(int i = 1; i <= m; i++) {
cin >> a[i].starting_hour >> a[i].ending_hour >> a[i].efficiency;
}
sort(a + 1, a + 1 + m, cmp);
int Max = 0;
for(int i = 1; i <= m; i++) dp[i] = a[i].efficiency;
for(int i = 1; i <= m; i++) {
for(int j = 1; j < i; j++) {
if(a[i].starting_hour >= a[j].ending_hour + r) {
dp[i] = max(dp[i], dp[j] + a[i].efficiency);
}
}
Max = max(Max, dp[i]);
}
cout << Max << endl;
return 0;
}
poj3280 Cheapest Palindorme
题意
给出一个字符串,再给出每个字母要删改的代价,问要把这个字符串改成一个回文串所需的最小代价
思路
这里有一个坑点,其实增加删除是一个骗局,因为一端删除和一端增加效果是一样的,所以只要把两者的最小值存下来就行了
我这里用一个二位数组来保存状态,f[i][j]表示把区间[i,j]改造成回文串所需的最小代价。我们来考虑一个问题,如果s[i] == s[j]这种情况下我们显然能得到f[i][j] = f[i + 1][j - 1](无论区间[i +1, j -1]是什么串,因为f[i + 1][j - 1]表示的是把区间[i + 1, j -1]改造成回文串的最小代价)那么如果s[i] != s[j] 呢?我们考虑当区间[i + 1][j]是回文串,对区间[i,j]来说,我只需要把s[i]删了,或者在j + 1的位置上增加s[i],就能得到一个回文串。我之前也说了实际增加和删除是一个道理也是体现在这。同理,当区间[i][j - 1]是回文串时,对区间[i,j]来说,我只要把s[j]删了,或者在i - 1的位置上加上s[j]就能得回文串。所以很容易得到f[i][j] = min(f[i + 1][j] + word[s[i]], f[i][j - 1] + word[s[j]])。
代码
#include <iostream>
using namespace std;
const int INF = 100000000;
int word_cost[500];
int dp[2000 + 10][2000 + 10];
int main() {
int n,m;
cin >> n >> m;
string s;
cin >> s;
for(int i = 1; i <= n; i++) {
char c;
int x,y;
cin >> c >> x >> y;
word_cost[c - 'a'] = min(x,y);
}
for(int i = 1; i <= m; i++)
dp[i][i] = 0;
for(int i = 2; i <= m; i++) {
for(int j = 0; j + i - 1 < m; j++) {
int left = j;
int right = j + i - 1;
//cout << left << "," << right << endl;
dp[left][right] = min(dp[left + 1][right] + word_cost[s[left] - 'a'],dp[left][right - 1] + word_cost[s[right] - 'a']);
if(s[left] == s[right]) {
dp[left][right] = dp[left + 1][right - 1];
}
}
}
cout << dp[0][m - 1] << endl;
return 0;
}