1. split_1
问题描述:将一个数组v分成a,b两组,设最小难度为min(a中相邻元素之差绝对值和+b中相邻元素之差绝对值和)
输入描述:输入n(2 <= n <=2000),表示数组长度,接下来输入n个数。
5
1 5 6 2 1
输出描述:
3
ps:分成1 2 1和5 6两组时,最小难度为3=2+1
[分析]:
状态描述:dp[i][j]表示第i个元素分给a,第j个元素分给b时的最小难度和。
状态转移方程:要结合递归去表述,dp[la][lb] = min(solve(now, lb) + (la ? abs(v[now] - v[la]) : 0), solve(la, now) + (lb ? abs(v[now] - v[lb]) : 0)),now=max(la, lb)+1.
时间复杂度分析:实际上搜索过程就是一个满二叉树,高度为n,时间复杂度为2^n,因此在搜索过程中,用到了记忆搜索,整个过程会小于计算的最大时间复杂度。
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;
#define MAX(a, b) ((a) > (b)? (a): (b))
#define MIN(a, b) ((a) < (b)? (a): (b))
const int maxn = 2e3 + 5;
int n, v[maxn], dp[maxn][maxn];
int solve(int la, int lb) {
int now = MAX(la, lb) + 1;
if(now == n + 1) return 0; //end
if(dp[la][lb] != -1) return dp[la][lb]; //已经求出此状态的最优解
return dp[la][lb] = MIN(solve(now, lb) + (la ? abs(v[now] - v[la]) : 0),
solve(la, now) + (lb ? abs(v[now] - v[lb]) : 0)); //下一个元素,给a或者给b
}
int main() {
while(~scanf("%d", &n)) {
v[0] = -1;
for(int i = 1; i <= n; i++) scanf("%d", &v[i]);
memset(dp, -1, sizeof(dp));
printf("%d\n", solve(0, 0));
}
return 0;
}
2. split_2
问题描述:在第一题基础上,让两个数组的和的差绝对值最小。而且还要求出两个数组。
输入输出:同上
[分析]:严格上讲,不能算作dp(希望有能有更好的解法),而是递归求解。把每一个可能的情况,搜索一遍,找出最优解。
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;
#define MAX(a, b) ((a) > (b)? (a): (b))
#define MIN(a, b) ((a) < (b)? (a): (b))
const int MAXN = 0x3f3f3f3f;//INT_MAX;
const int maxn = 1e2 + 5;
int n, v[maxn], dp[maxn][maxn], min_ans;
int solve(int a[], int label[], int lb, int lc, int sumb, int sumc) {
int now = MAX(lb, lc) + 1;
if(now == n + 1) return abs(sumb - sumc);
//if(dp[lb][lc] != -1) return dp[lb][lc];//完全搜索,求出最优值。
int res_b = solve(a, label, now, lc, sumb + a[now], sumc);
int res_c = solve(a, label, lb, now, sumb, sumc + a[now]);// + abs(sumb - sumc - a[now]); is multi
//cout << now << lb << lc << " " << " " << res_b << " " << res_c << endl;
if (res_b < res_c) {
if (res_b <= min_ans) {
label[now] = 0;
min_ans = res_b;
}
return res_b;
} else {
if (res_c <= min_ans) {
label[now] = 1;
min_ans = res_c;
}
return res_c;
}
}
int splitToMin(int a[], int b[], int c[]) {
memset(dp, -1, sizeof(dp));
int* label = (int*)malloc(sizeof(int) * (n + 1));
min_ans = MAXN;
int ans = solve(a, label, 0, 0, 0, 0);
int lb = 0, lc = 0;
for (int i = 1; i <= n; ++i) {
cout << label[i] << " " << a[i] << endl;
if (label[i] == 0) b[lb++] = a[i];
else c[lc++] = a[i];
}
free(label);
return ans;
}
int main() {
while(~scanf("%d", &n)) {
v[0] = -1;
for(int i = 1; i <= n; ++i) scanf("%d", &v[i]);
int* b = (int*)malloc(sizeof(int) * (n + 1));
int* c = (int*)malloc(sizeof(int) * (n + 1));
printf("%d\n", splitToMin(v, b, c));
free(b);
free(c);
}
return 0;
}
3. 构造回文串
问题描述:给定一个数组,长度为n(2<= n <= 1000),插入一些数字,使得该序列为回文串。
输入描述:输入n,接下来输出n个数字
5
1 2 3 1 2
输出描述:
11
[分析]这道题与第一题类似,也是递归求解,即,每个状态都要考虑是添加左边还是添加右边的数字。同时,这里用到了“记忆化搜索”,不然就会超时。递归公式:
dp[l, r] = MIN(solve(l, r - 1) + a[r], solve(l + 1, r) + a[l])