A : 石子合并
题目描述
将 堆石子绕圆形操场排放,现要将石子有序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。
请编写一个程序,读入堆数N及每堆的石子数,并进行如下计算:
选择一种合并石子的方案,使得做 次合并得分总和最大。
选择一种合并石子的方案,使得做 次合并得分总和最小。
输入描述
输入第一行一个整数N(1≤N≤100)(1≤N≤100),表示有N堆石子。
第二行N个整数,表示每堆石子的数量。
输出描述
输出共两行:
第一行为合并得分总和最小值,
第二行为合并得分总和最大值。
Case 1
Input
4
4 5 9 4
Output
43
54
#include <iostream>
#include <cstring>
// #include <queue>
// 14 18 22 54
// 8 13 22 43
typedef long long un;
un fmax(int i, int j, un **f, bool **mk, un *ns)
{
if (i == j) return 0;
if (!mk[i][j]) {
un maxs = 0;
for (int k = i; k < j; k++)
{
maxs = std::max(maxs, fmax(i, k, f, mk, ns) + fmax(k + 1, j, f, mk, ns));
}
// f[i][j] = maxs + ([&](int l, int r){ un a = 0; for (;l <= r; l++) { a += ns[l]; } return a; })(i, j);
mk[i][j] = true;
// std::cout << maxs << "|" << i << "-" << j << ", ";
for (int x = i; x <= j; x++)
{
maxs += ns[x];
}
f[i][j] = maxs;
}
return f[i][j];
}
un fmin(int i, int j, un **f, bool **mk, un *ns)
{
if (i == j) return 0;
if (!mk[i][j]) {
un mins = i == j ? 0 : 9999999;
for (int k = i; k < j; k++)
{
mins = std::min(mins, fmin(i, k, f, mk, ns) + fmin(k + 1, j, f, mk, ns));
}
f[i][j] = mins + ([&](int l, int r){ un a = 0; for (;l <= r; l++) { a += ns[l]; } return a; })(i, j);
mk[i][j] = true;
}
return f[i][j];
}
un del(bool max, un *lst, int n, int offset)
{
int xi = 0;
un **f = new un*[200];
bool **mk = new bool*[200];
un *ns = new un[200];
for (int i = 0; i < 200; i++)
{
f[i] = new un[200];
mk[i] = new bool[200];
memset(mk[i], 0, sizeof(bool) * 200);
}
memset(ns, 0, sizeof(un) * 200);
while (xi < n)
{
ns[xi] = max ? lst[xi + offset] : lst[xi + offset];
xi++;
}
un res = (max ? fmax(0, n - 1, f, mk, ns) : fmin(0, n - 1, f, mk, ns));
delete[] f;
delete[] mk;
delete[] ns;
return res;
}
int main()
{
un *lt;
un n, i = 0;
std::cin >> n;
lt = new un[n * 2];
while (i < n)
{
std::cin >> lt[i];
lt[i + n] = lt[i];
i++;
}
un mx = del(true, lt, n, 0);
un mn = del(false, lt, n, 0);
for (int i = 1; i < n; i++)
{
mx = std::max(mx, del(true, lt, n, i));
mn = std::min(mn, del(false, lt, n, i));
}
std::cout << mn << std::endl << mx << std::endl;
return 0;
}
B : 括号序列
题目描述
定义一个合法的括号序列
空序列合法
如果S合法,那么(S)和[S]合法
假设A和B合法,那么AB合法
合法序列 - (),[],(()),([]),()[],()[()]
非法序列 - (,[,],)(, (]),([(),([)]
给定一个序列,求最少添加多少个括号,才能得到一个合法序列。
输入描述
输入仅一行,为一个字符串S
输出描述
输出只有一个整数,表示增加的最少字符数。
Case 1
Input
[])
Output
1
#include <iostream>
#include <iostream>
int f(int i, int j, std::string& s, bool **mk, int **fr)
{
int mn = 999999;
if (mk[i][j]) return fr[i][j];
if (i == j) return 1;
if (i > j) return 0;
if (i + 1 == j)
{
if (s[i] == '(' && s[j] == ')') return 0;
if (s[i] == '[' && s[j] == ']') return 0;
if (s[i] == '{' && s[j] == '}') return 0;
return 2;
}
for (int k = i; k < j; k++)
{
mn = std::min(f(i, k, s, mk, fr) + f(k + 1, j, s, mk, fr), mn);
}
if (s[i] == '(' && s[j] == ')') mn = std::min(f(i + 1, j - 1, s, mk, fr), mn);
if (s[i] == '[' && s[j] == ']') mn = std::min(f(i + 1, j - 1, s, mk, fr), mn);
if (s[i] == '{' && s[j] == '}') mn = std::min(f(i + 1, j - 1, s, mk, fr), mn);
if (s[i] == '(' && s[j] != ')') mn = std::min(f(i + 1, j, s, mk, fr) + 1, mn);
if (s[i] == '[' && s[j] != ']') mn = std::min(f(i + 1, j, s, mk, fr) + 1, mn);
if (s[i] == '{' && s[j] != '}') mn = std::min(f(i + 1, j, s, mk, fr) + 1, mn);
if (s[i] != '(' && s[j] == ')') mn = std::min(f(i, j - 1, s, mk, fr) + 1, mn);
if (s[i] != '[' && s[j] == ']') mn = std::min(f(i, j - 1, s, mk, fr) + 1, mn);
if (s[i] != '{' && s[j] == '}') mn = std::min(f(i, j - 1, s, mk, fr) + 1, mn);
fr[i][j] = mn;
mk[i][j] = true;
return mn;
}
int main()
{
bool **mk;
int **fr;
std::string s;
std::cin >> s;
mk = new bool*[s.size()];
fr = new int*[s.size()];
for (int i = 0; i < s.size(); i++)
{
mk[i] = new bool[s.size()];
fr[i] = new int[s.size()];
for (int j = 0; j < s.size(); j++)
{
mk[i][j] = false;
}
}
std::cout << f(0, s.size() - 1, s, mk, fr) << std::endl;
return 0;
}
C : 最长回文子序列
题目描述
给定一个字符串S ,找到其中最长的回文子序列,并返回该序列的长度。
输入描述
输入为一个只包含小写英文字母的字符串S,最大长度不超过3000。
输出描述
输出最长回文子序列的长度。
Case 1
Input
abcb
Output
3
#include <iostream>
std::string str;
bool mk[4000][4000];
int fr[4000][4000];
int f(int i, int j)
{
if (i == j) return 1;
if (i > j) return 0;
if (mk[i][j]) return fr[i][j];
if (str[i] == str[j])
{
mk[i][j] = true;
fr[i][j] = f(i + 1, j - 1) + 2;
return fr[i][j];
}
fr[i][j] = std::max(f(i + 1, j), f(i, j - 1));
mk[i][j] = true;
return fr[i][j];
}
int main()
{
std::cin >> str;
std::cout << f(0, str.size() - 1);
return 0;
}
D : 方格填充
题目描述
将高为H,宽为W的棋盘分割成若干个宽和高分别为1和2的长方形,有多少种方案。
输入描述
第一行为H(1≤N≤11)(1≤N≤11),W(1≤W≤11)(1≤W≤11)。
输出描述
输出方案数。
Case 1
Input
2 2
Output
2
#include <iostream>
#include <cstring>
using namespace std;
typedef long long un;
const un maxN = 12;
un f[maxN][1 << 12];
un sulv[9999999][2];
un res;
int H, W;
int main()
{
res = 0;
cin >> H >> W;
for(int i = 0; i < (1 << W); i++)
{
for(int j = 0; j < (1 << W); j++)
{
bool ok = true;
for(int k = 0; k < W; k++)
{
if(ok)
{
if((i & (1 << k)) == 0)
{
if((j & (1 << k)) == 0)
{
ok = false;
break;
}
}
else
{
if((j & (1 << k)) == 0)
continue;
k++;
if(k >= W || ((i & (1 << k)) == 0))
{
ok = false;
break;
}
else
{
if((j & (1 << (k - 1))) && (j & (1 << (k))) == 0)
{
ok = false;
break;
}
else if((j & (1 << (k - 1))) == 0 && (j & (1 << (k))))
k--;
}
}
}
}
if(ok)
{
sulv[res][0] = i;
sulv[res++][1] = j;
}
}
}
memset(f, 0, sizeof(f));
f[0][(1 << W) - 1] = 1;
for(int i = 0; i < H; i++)
{
for(int j = 0; j < res; j++)
{
f[i + 1][sulv[j][1]] += f[i][sulv[j][0]];
}
}
cout << f[H][(1 << W) - 1];
return 0;
}
思路
最后一题:
课上基本讲的差不多了,自己做的话还是挺有难度的。