20220403 模拟赛 总结

20220403 模拟赛

象棋

题意

给一个 \(n\times m\) 的棋盘,两个玩家有 'S''M' 两种国王,国王八向移动

传播值定义为玩家国王两两之间距离和,要分别求两个玩家的传播值

sol & code

切比雪夫距离板题

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int N = 1005;
int n, m, tot;
struct poi { int x, y; } a[N * N];
LL tt, sm;
char mp[N][N];
inline bool cmp1(poi A, poi B) { return A.x < B.x; }
inline bool cmp2(poi A, poi B) { return A.y < B.y; }
inline void sol(char Ch) {
    tot = 0;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            if (mp[i][j] == Ch) a[++tot] = (poi){ i - j, i + j };
    sort(a + 1, a + tot + 1, cmp1);
    sm = tt = 0;
    for (int i = 1; i <= tot; i++) sm += a[i].x * (i - 1) - tt, tt += a[i].x;
    sort(a + 1, a + tot + 1, cmp2), tt = 0;
    for (int i = 1; i <= tot; i++) sm += a[i].y * (i - 1) - tt, tt += a[i].y;
    printf("%lld ", sm / 2);
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) scanf("%s", mp[i] + 1);
    sol('M'), sol('S');
}

小球的颜色

题意

\(n\) 个小球,颜色为 \(c_i\) 。每次询问 \([l,r]\) 区间内小球有多少种不同颜色

\(n,q\le 5\times 10^5\)

sol

  • 一眼莫队,奇偶优化能勉强过,\(O(n\sqrt n)\)

  • 离线处理,按 \(r\) 排序,对于同一种球,只需保留最后一个出现的位置

    如当前到第 \(i\) 个球,将该颜色球的位置替换为 \(i\)

    只需查询 \([l,r]\) 中球的个数,用一个 BIT 即可

    复杂度 \(O(n\log n)\)

code

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int N = 500005;
int n, m, a[N], ans[N], t[N];
struct qry { int l, r, id; } q[N];
inline bool cmp(qry A, qry B) {
    return A.r < B.r;
}
inline void add(int p, int v) { for (; p <= n; p += p & -p) t[p] += v; }
inline int ask(int p) { register int re = 0; for (; p; p -= p & -p) re += t[p]; return re; }
int pos[N];
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    for (int i = 1; i <= m; i++) scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;
    sort(q + 1, q + m + 1, cmp);
    for (int i = 1, j = 1; i <= n; i++) {
        if (pos[a[i]]) add(pos[a[i]], -1);
        add(i, 1), pos[a[i]] = i;
        while (j <= m && q[j].r <= i) ans[q[j].id] = ask(q[j].r) - ask(q[j].l - 1), j++;
    }
    for (int i = 1; i <= m; i++) printf("%d\n", ans[i]);
}

魔法师

题意

\(n\times m\) 的迷宫,'#' 是墙,'.' 是路,魔法师要从 \((sx,sy)\) 移动到 \((ex,ey)\)

他可以使用魔法,移动到以当前点为中心的 \(5\times 5\) 正方形区域内的任意一个 '.' 的格

问最少需要多少次魔法,或者输出 -1

sol

这是一个典型的 0/1 bfs 问题,即边权只有 0/1 两种情况

  • 具体:用一个 deque ,对于边权为 0 的边到的点加入队头,为 1 的边到的点加入对尾

    实质是实现了优先级

  • 实现方面:对于记忆化的处理不够熟练

code

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int N = 1005, INF = 0x3f3f3f3f;
const int dx[5] = { 0, 0, 1, -1 }, dy[5] = { 1, -1, 0, 0 };
int n, m, sx, sy, ex, ey, tot, a[N][N];
char mp[N][N];
struct poi { int x, y; } nw;
deque<poi> Q;
int main() {
    scanf("%d%d%d%d%d%d", &n, &m, &sx, &sy, &ex, &ey);
    for (int i = 1; i <= n; i++) scanf("%s", mp[i] + 1);
    for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) a[i][j] = INF;
    Q.push_back((poi){ sx, sy });
    a[sx][sy] = 0;
    while (!Q.empty()) {
        nw = Q.front(), Q.pop_front();
        register int nx = nw.x, ny = nw.y;
        if (nx == ex && ny == ey) return printf("%d", a[nx][ny]), 0;
        for (int i = 0, xx, yy; i < 4; i++) {
            xx = nx + dx[i], yy = ny + dy[i];
            if (xx < 1 || xx > n || yy < 1 || yy > m || mp[xx][yy] == '#') continue;
            if (a[nx][ny] < a[xx][yy]) a[xx][yy] = a[nx][ny], Q.push_front((poi){ xx, yy });
        }
        for (int xx = nx - 2; xx <= nx + 2; xx++)
            for (int yy = ny - 2; yy <= ny + 2; yy++) {
                if (xx < 1 || xx > n || yy < 1 || yy > m || mp[xx][yy] == '#') continue;
                if (a[nx][ny] + 1 < a[xx][yy]) a[xx][yy] = a[nx][ny] + 1, Q.push_back((poi){ xx, yy });
            }
    }
    puts("-1");
}

表格取数

题意

\(n\times m\) 的表格有 \(K\) 个数,只能向右或向下,一行最多取 3 个数,求取得数之和最大

\(n,m\le3000\)

sol & code

\(f_{i,j,k}\) 为在 \(i,j\) 且取了 \(k\) 个,直接 dp,\(k\) 是常数,故复杂度为 \(O(nm)\)

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int N = 3005;
int n, m, K;
LL a[N][N], ans, f[N][N][5];
inline void cmx(LL &x, LL y) { x < y ? x = y : 1; }
int main() {
    scanf("%d%d%d", &n, &m, &K);
    for (int i = 1, x, y, z; i <= K; i++) {
        scanf("%d%d%d", &x, &y, &z);
        a[x][y] = 1ll * z;
    }
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) {
            for (int k = 0; k <= 3; k++) cmx(f[i][j][0], f[i - 1][j][k]);
            for (int k = 0; k <= 3; k++) cmx(f[i][j][k], f[i][j - 1][k]);
            if (a[i][j]) for (int k = 3; k; k--) cmx(f[i][j][k], f[i][j][k - 1] + a[i][j]);
        }
    for (int i = 0; i <= 3; i++) ans = max(ans, f[n][m][i]);
    printf("%lld", ans);
}

构造回文串

题意

\(n\) 个字符串,第 \(i\) 个为 \(s_i\) ,可以选出若干字符串,按任意顺序连接,要构成一个回文串

选第 \(i\) 个代价为 \(c_i\) ,若选多次代价也会重复计算,求最小代价

\(n\le 50,|S_i|\le 20\)

sol

考虑从左右两边分别构建字符串,我们希望最后能将左边部分和右边部分进行匹配

这里的匹配意思是:对于串 \(A\) 和串 \(B\) ,较短的那串是较长的那串的字串

若匹配后,剩下一个回文串(或为空),则成功构建回文

可以每次把某个字符串加入左边或右边,得到一个中间状态

  • 在转移过程中,保证始终至多一边未匹配,其余部分匹配

    即如左边有未匹配的部分,就把新字符串往右加,反之相同

这样,只需保存当前为匹配部分,以及哪边未匹配。

  • 因为总是在相反的一边添加,且未匹配部分一定为原来某个字符串的前缀或后缀

    总状态数限制为 \(O(N|S|)\)

其实这是一个最短路,数据足够小,可用各种算法,甚至直接搜索

  • 实现:用 stl 可以方便地实现字符串的记忆化

code

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int N = 55;
string a[N][2];
int n;
LL b[N], ans;
unordered_map<string, LL> f[2];
unordered_map<string, bool> vis[2];
inline bool chk(string s) {
    register int l = s.size();
    for (int i = 0; i < l / 2; i++)
        if (s[i] != s[l - i - 1])
            return 0;
    return 1;
}
LL dfs(string s, int p) {
    if (f[p].count(s))
        return f[p][s];
    if (chk(s))
        return 0;
    if (vis[p].count(s))
        return 1e18;
    register int ls = s.size(), lt, l;
    register LL res, ans = 1e18;
    string t, ps, pt;
    vis[p][s] = 1;
    for (int i = 1; i <= n; i++) {
        t = a[i][p ^ 1], lt = t.size(), l = min(ls, lt);
        ps = s.substr(0, l), pt = t.substr(0, l);
        if (ps != pt)
            continue;
        if (ls > lt)
            res = dfs(s.substr(l, ls - l), p);
        else
            res = dfs(t.substr(l, lt - l), p ^ 1);
        ans = min(ans, res + b[i]);
    }
    vis[p][s] = 0;
    return f[p][s] = ans;
}
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        cin >> a[i][0] >> b[i];
        a[i][1] = a[i][0];
        reverse(a[i][1].begin(), a[i][1].end());
    }
    ans = 1e18;
    for (int i = 1; i <= n; i++) ans = min(ans, dfs(a[i][0], 0) + b[i]);
    if (ans < 1e18)
        printf("%lld", ans);
    else
        puts("-1");
}

总结

  • 学到了切比雪夫距离、01bfs
  • 区间询问摆脱莫队的思维定式
  • 最后一题的状态限制,转换巧妙
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值