算法基础 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(),则键值存在

posted @ 2022-05-11 20:47  四季夏目天下第一  阅读(17)  评论(0编辑  收藏  举报