算法基础 Final 题目总结
简单的整数划分问题
将正整数 \(n\) 表示成一系列正整数之和,\(n=n_1+n_2+…+n_k\), 其中\(n_1>=n_2>=…>=n_k>=1 ,k>=1\) 。
正整数 \(n\) 的这种表示称为正整数 \(n\) 的划分,正整数 \(n\) 的不同的划分个数称为正整数 \(n\) 的划分数。
求 \(n\) 的划分数
观察到划分序列与顺序无关(即物品只有质量特性)
实际上就是背包问题:背包容量为 \(n\),物品重量为 \(1\) 到 \(n\),数量无限,求装满背包的方案数
int main() {
g[0] = 1;
for (int i = 1; i <= 50; ++i)
for (int j = i; j <= 50; ++j)
g[j] += g[j - i];
int n;
while (cin >> n) cout << g[n] << endl;
return 0;
}
复杂的整数划分问题
\(N\) 划分成 \(K\) 个正整数之和的划分数目
\(f[n][k]\) 为 \(n\) 划分为 \(k\) 个正整数的划分数,状态的转移有点 \(tricky\)
将有 \(1\) 和无 \(1\) 的状态分开转移
f[0][0] = 1;
for (int i = 1; i <= 50; ++i)
for (int j = 1; j <= i; ++j)
f[i][j] += f[i - j][j] + f[i - 1][j - 1];
// f[i-j][j] 这个划分中每个数都 +1,所以这个状态的转移无 1
// f[i-1][j-1] 这个划分中至少有一个 1,所以这个状态的转移有 1
ans = f[n][k];
\(N\) 划分成若干个不同正整数之和的划分数目
背包容量为 \(n\),物品重量为 \(1\) 到 \(n\),每个物品只有一个
g[0] = 1;
for (int i = 1; i <= 50; ++i)
for (int j = 50; j >= i; --j) // 倒序枚举
g[j] += g[j - i];
ans = g[n];
\(N\) 划分成若干个奇正整数之和的划分数目
背包容量为\(n\),物品重量为 \(1,3,5...\) ,物品数量无限
h[0] = 1;
for (int i = 1; i <= 50; i += 2)
for (int j = 1; j <= 50; ++j) {
if (j - i < 0) continue;
h[j] += h[j - i];
}
拯救公主
留个传送门
求最短路径,很明显的 \(BFS\),但有一个问题:由于路径上增添了经停点,很有可能走回头路,传统的 \(vis\) 标记数组不能用了
实际上 \(vis\) 标记的是一个状态,在普通的图中这个状态是单一的,即来没来过该点
在这道题中,我们为 \(vis\) 增添一维记录收集到的宝石的种类即可
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int MAX_N = 205;
const int INF = 0x7fffffff;
const int dx[4] = {-1, 1, 0, 0};
const int dy[4] = {0, 0, -1, 1};
int r, c, k;
char a[MAX_N][MAX_N];
int f[MAX_N][MAX_N][50];
int portalCnt;
int portalx[MAX_N], portaly[MAX_N];
struct Point {
int x, y, d, gem;
Point(int _x, int _y, int _d = 0, int _g = 0) : x(_x), y(_y), d(_d), gem(_g) {}
};
int main() {
int t;
cin >> t;
while (t--) {
cin >> r >> c >> k;
int sx, sy, tx, ty;
portalCnt = 0;
for (int i = 1; i <= r; ++i)
for (int j = 1; j <= c; ++j) {
cin >> a[i][j];
if (a[i][j] == 'S')
sx = i, sy = j;
if (a[i][j] == 'E')
tx = i, ty = j;
if (a[i][j] == '$') {
++portalCnt;
portalx[portalCnt] = i, portaly[portalCnt] = j;
}
}
for (int i = 0; i <= r + 1; ++i) a[i][0] = a[i][c + 1] = '#';
for (int i = 0; i <= c + 1; ++i) a[0][i] = a[r + 1][i] = '#';
memset(f, 0, sizeof (f));
int flag = 1;
queue<Point> que;
que.push(Point(sx, sy));
while (!que.empty()) {
Point cur = que.front();
int x = cur.x, y = cur.y, d = cur.d, gem = cur.gem;
que.pop();
if (f[x][y][gem]) continue;
f[x][y][gem] = d;
if (x == tx && y == ty) {
int cnt = 0;
for (int i = 0; i < 5; ++i)
if ((1 << i) & gem) ++cnt;
if (cnt >= k) {
flag = 0;
cout << d << endl;
break;
}
}
for (int i = 0; i < 4; ++i) {
int nex = x + dx[i], ney = y + dy[i], negem = gem;
if (a[nex][ney] == '#') continue;
if (a[nex][ney] == '$') {
for (int i = 1; i <= portalCnt; ++i) {
if (portalx[i] == nex && portaly[i] == ney) continue;
if (!f[portalx[i]][portaly[i]][gem])
que.push(Point(portalx[i], portaly[i], d + 1, gem));
}
}
if (isdigit(a[nex][ney]))
negem |= (1 << (a[nex][ney] - '0'));
if (!f[nex][ney][negem])
que.push(Point(nex, ney, d + 1, negem));
}
}
if (flag)
cout << "oop!" << endl;
}
return 0;
}
还有在 \(debug\) 时找到的问题,\(BFS\) 时入队的点离起点的距离一定要保证是连续递增的,不然无法满足靠起点近的点先扩展的原则
怀表问题
传送门
\(POJ\) 上 \(AC\) 了,\(coursera\) 上 \(CE\) 了,好像是 \(coursera\) 编译器不支持 to_string
函数
这道题我的思路很连贯,首先观察到表链只有 \(4\) 种,很容易想到 \(O(4^40)\) 的深搜方案
写出深搜发现状态是有限的,参数只有表链尾端与剩余四种表链的个数,直接上记忆化,\(dp\) 呼之欲出
唯一的问题就是记忆化数组会 \(MLE\),只好采用了一个比较暴力的 \(map\) 做法
上网发现有 \(hash\) 存状态的,但我怕冲突,,
#include <iostream>
#include <cstring>
#include <map>
#include <string>
using namespace std;
const int MAX_N = 41;
int cnt[4];
map<string, long long> mem[2];
long long DFS(char head, char tail, int lev) {
if (!lev) {
if (head == tail) return 1;
return 0;
}
int sgn = (head == 'V');
string sta = to_string(cnt[0]) + " " + to_string(cnt[1]) + " " +
to_string(cnt[2]) + " " + to_string(cnt[3]);
if (mem[sgn].find(sta) != mem[sgn].end())
return mem[sgn][sta];
long long res = 0;
if (head == 'V') {
if (cnt[2] > 0) {
cnt[2]--;
res += DFS('L', tail, lev - 1);
cnt[2]++;
}
if (cnt[3] > 0) {
cnt[3]--;
res += DFS('V', tail, lev - 1);
cnt[3]++;
}
}
if (head == 'L') {
if (cnt[0] > 0) {
cnt[0]--;
res += DFS('L', tail, lev - 1);
cnt[0]++;
}
if (cnt[1] > 0) {
cnt[1]--;
res += DFS('V', tail, lev - 1);
cnt[1]++;
}
}
return mem[sgn][sta] = res;
}
int main() {
int n, k;
while (cin >> n >> k) {
string o, s;
cin >> o;
memset(cnt, 0, sizeof (cnt));
mem[0].clear();
mem[1].clear();
for (int i = 1; i <= n; ++i) {
cin >> s;
if (s == "LL") cnt[0]++;
if (s == "LV") cnt[1]++;
if (s == "VL") cnt[2]++;
if (s == "VV") cnt[3]++;
}
long long ans = DFS(o[1], o[0], k);
if (!ans) cout << "NO" << endl;
else cout << "YES\n" << ans << endl;
}
return 0;
}
另外,关于在map
中寻找某个键值 key
是否存在的操作:
mapName[key]
:使用中括号访问不仅判断不了 key
是否存在(除非将 \(0\) 视作空),还会创建 key
键值
mapName.at(key)
: 若 key
不存在,会抛出异常
mapName.find(key)
:这是比较好的办法,若返回的迭代器不为mapName.end()
,则键值存在