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
- 区间询问摆脱莫队的思维定式
- 最后一题的状态限制,转换巧妙