JZOJ 2022.01.21【提高A组】模拟
简要题解加心得
不得不说这是我打得比较痛苦且改得比较痛苦的一套题了
\(\text{T1 1085. 【GDOI2008】彩球游戏}\)
整整改了三个半小时
直接崩溃了
明明本地可以跑过去,偏偏 \(GMOJ\) 评测机很强势
考场没有想双向 \(BFS\),只单向搜了
而且还用了 \(STL\) 给予的“巨快” \(\text{unordered_map queue}\)
改题时沿用这两个玩意,实在太慢了
一怒之下手打循环队列和哈希表
还是手打的快啊
这种拼常数的暴搜尽量少用 \(STL\)
很容易想到把状态压成三进制,双向 \(BFS\)
也是第一次打双向 \(BFS\)
发现当一个队列某层拓展出去一个点能得到答案后,要把两个队列当前层拓展完才能退出(因为同一层可能有更优答案)
用一个标记 \(flag\) 标记这层即可
\(\text{Code}\)
#include <cstdio>
#include <cstring>
#include <iostream>
#define IN inline
#define RE register
using namespace std;
int n, m;
char s[5];
struct node{
int m[4][4];
IN node(){
for(RE int i = 0; i < 4; i++)
for(RE int j = 0; j < 4; j++) m[i][j] = 0;
}
}a, b;
const int mod = 1e6 + 7, INF = 2e9, SIZE = 2e6;
struct point{int c, s;};
struct Queue{
point Q[SIZE]; int head, tail;
IN Queue(){head = tail = 0;}
IN void push(point x){++tail; if (tail >= SIZE) tail = 0; Q[tail] = x;}
IN point front(){int t = head + 1; if (t >= SIZE) t = 0; return Q[t];}
IN void pop(){++head; if (head >= SIZE) head = 0;}
IN int empty(){return (head == tail);}
}Q[2];
struct Hash_table{
int tot, h[mod];
struct edge{int val, nxt, w;}e[3000005];
IN void clear(){memset(h, 0, sizeof h), tot = 0;}
IN void insert(int s, int w){int t = s % mod; e[++tot] = edge{s, h[t], w}, h[t] = tot;}
IN int find(int s)
{
int t = s % mod;
for(RE int i = h[t]; i; i = e[i].nxt) if (e[i].val == s) return e[i].w;
return 0;
}
}vis[2];
IN int calc(node a)
{
int val = 0;
for(RE int i = 0; i < n; i++)
for(RE int j = 0; j < m; j++) val = val * 3 + a.m[i][j];
return val;
}
IN node trans(int s)
{
int x = n - 1, y = m - 1; node b;
while (s){b.m[x][y] = s % 3, s /= 3; --y; if (y == -1) y = m - 1, --x;}
return b;
}
IN int BFS()
{
if (calc(a) == calc(b)) return 0;
int z, res = INF, p = 0, flag = INF, cnt = 0; point x; node cur, now;
while (!Q[0].empty()) Q[0].pop();
while (!Q[1].empty()) Q[1].pop();
vis[0].clear(), vis[1].clear();
Q[0].push(point{1, z = calc(a)}), vis[0].insert(z, 1);
Q[1].push(point{1, z = calc(b)}), vis[1].insert(z, 1);
for(; !Q[0].empty() || !Q[1].empty(); p ^= 1)
{
if (cnt > 1) return res;
if (Q[p].empty()) continue;
x = Q[p].front(), Q[p].pop();
if (x.c > flag){++cnt; continue;}
if (z = vis[p ^ 1].find(x.s)) res = min(res, x.c + z - 2), flag = x.c;
now = trans(x.s);
for(RE int i = 1; i < n; i++)
for(RE int j = 1, k; j < m; j++)
{
cur = now, k = cur.m[i][j];
if (!p) cur.m[i][j] = cur.m[i-1][j], cur.m[i-1][j] = cur.m[i-1][j-1], cur.m[i-1][j-1] = cur.m[i][j-1], cur.m[i][j-1] = k;
else cur.m[i][j] = cur.m[i][j-1], cur.m[i][j-1] = cur.m[i-1][j-1], cur.m[i-1][j-1] = cur.m[i-1][j], cur.m[i-1][j] = k;
if (k = vis[p ^ 1].find(z = calc(cur))) res = min(res, x.c + k - 1), flag = x.c;
if (!vis[p].find(z)) vis[p].insert(z, x.c + 1), Q[p].push(point{x.c + 1, z});
cur = now, cur.m[i][j] += p+1, cur.m[i-1][j] += p+1, cur.m[i-1][j-1] += p+1, cur.m[i][j-1] += p+1;
cur.m[i][j] %= 3, cur.m[i-1][j] %= 3, cur.m[i-1][j-1] %= 3, cur.m[i][j-1] %= 3;
if (k = vis[p ^ 1].find(z = calc(cur))) res = min(res, x.c + k - 1), flag = x.c;
if (!vis[p].find(z)) vis[p].insert(z, x.c + 1), Q[p].push(point{x.c + 1, z});
}
}
return (res == INF ? -1 : res);
}
int main()
{
scanf("%d", &n);
while (n)
{
scanf("%d", &m);
for(RE int i = 0; i < n; i++)
{
scanf("%s", s);
for(RE int j = 0; j < m; j++)
if (s[j] == 'R') a.m[i][j] = 0;
else if (s[j] == 'B') a.m[i][j] = 1;
else a.m[i][j] = 2;
}
for(RE int i = 0; i < n; i++)
{
scanf("%s", s);
for(RE int j = 0; j < m; j++)
if (s[j] == 'R') b.m[i][j] = 0;
else if (s[j] == 'B') b.m[i][j] = 1;
else b.m[i][j] = 2;
}
printf("%d\n", BFS()), scanf("%d", &n);
}
}
\(\text{T2 1087. 【NOIP动态规划专题】鱼肉炸弹}\)
感觉比较难想
因为高度互不相同,一个高度可控区间是连续的
发现将区间最大值抽出来是一棵二叉树
那么就可以树形 \(dp\) 了
设 \(f_{i,j}\) 表示在 \(i\) 子树中用了 \(j\) 个炸弹的答案
转移枚举左右子树分别放多少炸弹,子树的根放不放即可
\(\text{Code}\)
#include <cstdio>
#include <iostream>
#include <cstring>
#define LL long long
#define RE register
using namespace std;
const int N = 1e5 + 5;
const LL INF = 0x3f3f3f3f3f3f3f3f;
int n, m, rt, h[N], c[N], s[N], ls[N], rs[N];
LL f[N][6];
int build(int l, int r)
{
if (l > r) return 0;
if (l == r) return l;
int mid = 0;
for(RE int i = l; i <= r; i++) if (h[i] > h[mid]) mid = i;
ls[mid] = build(l, mid - 1), rs[mid] = build(mid + 1, r);
return mid;
}
LL DP(int x)
{
if (!x) return 0;
DP(ls[x]), DP(rs[x]);
for(RE int i = 0; i <= m; i++)
for(RE int j = 0; i + j <= m; j++)
{
f[x][i + j] = min(f[x][i + j], max(f[ls[x]][i], f[rs[x]][j]) + c[x]);
if (i + j < m) f[x][i + j + 1] = min(f[x][i + j + 1], max(f[ls[x]][i], f[rs[x]][j]));
}
return f[x][m];
}
int main()
{
scanf("%d%d", &n, &m);
for(RE int i = 1; i <= n; i++) scanf("%d%d", &h[i], &c[i]);
for(RE int i = 1; i <= n; i++)
for(RE int j = 0; j <= m; j++) f[i][j] = INF;
rt = build(1, n), printf("%lld\n", DP(rt));
}
\(\text{T3 1100. 【GDOI2008】狐狸的谜语}\)
考场没有注意到 \(0\) 与后面的大数乘起来可变 \(0\) 的情况(光荣爆0)
正解是区间 \(dp\)
实际上暴搜剪枝非常快,\(dfs\) 记录形如 \(p+q*[x..n]\) 这样的形式即可
大力讨论填加和乘对这种形式的影响
一般的剪枝:步数大于等于目前答案,加数太大,乘数太大且后一位不是 \(0\)
\(\text{Code}\)
#include <cstdio>
#include <cstring>
#define RE register
using namespace std;
const int N = 205;
int n, T, res;
char str[25];
void dfs(int x, int p, int q, int step)
{
if (res != -1 && step >= res) return;
if (x > n)
{
int f = 0;
if (p == -1 || q == -1) f = (q == T || p == T);
else if (p != -1 && q != -1) f = (p + q == T);
return (f ? res = step : -1), void();
}
if (p > T || (q > T && str[x] != '0')) return;
int s = 0;
for(RE int i = x, y; i <= n; i++)
{
s = s * 10 + (str[i] ^ 48), y = (i != n);
if (p == -1 && q == -1) dfs(i + 1, s, -1, step + y), dfs(i + 1, -1, s, step + y);
else if (p != -1 && q != -1) dfs(i + 1, p + q * s, -1, step + y), dfs(i + 1, p, q * s, step + y);
else if (p == -1) dfs(i + 1, q * s, -1, step + y), dfs(i + 1, -1, q * s, step + y);
else dfs(i + 1, p + s, -1, step + y), dfs(i + 1, p, s, step + y);
}
}
int main()
{
scanf("%s%d", str + 1, &T);
while (T != -1)
n = strlen(str + 1), res = -1, dfs(1, -1, -1, 0), printf("%d\n", res), scanf("%s%d", str + 1, &T);
}
\(\text{T4 1160. 【GDOI2008】酱油推广计划}\)
考场唯一过的题,很明显的一个 \(Tarjan\) 缩点加 \(dp\)
题目告诉我们一个点只属于一个环
不难想到 \(Tarjan\) 缩点后 \(dp\),设 \(F[i]\) 表示到i点最长路径
与 \(LG\) 模板不同的是,这里每个点只能经过一次
于是 \(dp\) 转移时要考虑环内 \(dp\) 值的更新
先更新环内的再让环转移出去
按拓扑序 \(dp\)
考场比较傻,完全是个一维的 \(dp\) 硬是套成了二维
莫名其妙加了 \(F_{i,j}\) 的前一维表示在哪个环内
写起来麻烦了
考场代码瞅瞅
\(\text{Code}\)
#include <cstdio>
#include <iostream>
#include <cstring>
#define RE register
#define IN inline
using namespace std;
const int N = 1e3 + 5;
int n, m, h1[N][N], h2[N][N];
int dfn[N], low[N], col[N], vis[N], st[N], top, dfc, color;
void tarjan(int x)
{
dfn[x] = low[x] = ++dfc, st[++top] = x, vis[x] = 1;
for(RE int i = 1; i <= n; i++)
if (h1[x][i])
if (!dfn[i]) tarjan(i), low[x] = min(low[x], low[i]);
else if (vis[i]) low[x] = min(low[x], dfn[i]);
if (dfn[x] == low[x])
{
col[x] = ++color, vis[x] = 0;
while (st[top] ^ x) col[st[top]] = color, vis[st[top]] = 0, --top;
--top;
}
}
int g[N][N], f[N][N], in[N], Q[N], ff[N][N], sum[N][N], up[N];
void make_circle(int x, int co)
{
g[co][++g[co][0]] = x, vis[x] = 1;
for(RE int i = 1; i <= n; i++) if (col[i] == co && !vis[i]) make_circle(i, co);
}
IN void prepare()
{
for(RE int i = 1; i <= color; i++)
{
for(RE int j = 2; j <= g[i][0]; j++)
sum[i][j] = sum[i][j - 1] + h1[g[i][j - 1]][g[i][j]];
up[i] = sum[i][g[i][0]] + h1[g[i][g[i][0]]][g[i][1]];
}
}
IN int Get(int z, int l, int r){return sum[z][r] - sum[z][l - 1];}
int Topsort()
{
int head = 0, tail = 0; memset(vis, 0, sizeof vis); prepare();
for(RE int i = 1; i <= color; i++) if (!in[i]) Q[++tail] = i, vis[i] = 1;
while (head < tail)
{
int z = Q[++head];
for(RE int i = 1; i <= color; i++)
if (h2[z][i] && !vis[i]){--in[i]; if (!in[i]) Q[++tail] = i;}
for(RE int i = 1; i <= g[z][0]; i++) ff[z][g[z][i]] = f[z][g[z][i]];
for(RE int i = 1; i <= g[z][0]; i++)
{
for(RE int j = 1; j < i; j++) f[z][g[z][i]] = max(f[z][g[z][i]], ff[z][g[z][j]] + Get(z, j, i));
for(RE int j = i + 1; j <= g[z][0]; j++)
f[z][g[z][i]] = max(f[z][g[z][i]], ff[z][g[z][j]] + up[z] - Get(z, i, j));
}
for(RE int i = 1; i <= g[z][0]; i++)
for(RE int j = 1; j <= n; j++)
if (h1[g[z][i]][j]) f[col[j]][j] = max(f[col[j]][j], f[z][g[z][i]] + h1[g[z][i]][j]);
}
int res = 0;
for(RE int i = 1; i <= color; i++)
for(RE int j = 1; j <= g[i][0]; j++) res = max(res, f[i][g[i][j]]);
return res;
}
int main()
{
scanf("%d%d", &n, &m);
for(RE int i = 1, u, v, w; i <= m; i++) scanf("%d%d%d", &u, &v, &w), h1[u][v] = max(h1[u][v], w);
for(RE int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i); memset(vis, 0, sizeof vis);
for(RE int i = 1; i <= n; i++) if (!vis[i]) make_circle(i, col[i]);
for(RE int i = 1; i <= n; i++)
for(RE int j = 1; j <= n; j++)
if (h1[i][j] && col[i] ^ col[j]) h2[col[i]][col[j]] = h1[i][j], ++in[col[j]];
printf("%d\n", Topsort());
}