简要题解加心得

不得不说这是我打得比较痛苦且改得比较痛苦的一套题了

\(\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());
}

JZOJ 2022.01.21【提高A组】模拟的更多相关文章

  1. JZOJ 100029. 【NOIP2017提高A组模拟7.8】陪审团

    100029. [NOIP2017提高A组模拟7.8]陪审团 Time Limits: 1000 ms  Memory Limits: 131072 KB  Detailed Limits   Got ...

  2. JZOJ 5818. 【NOIP提高A组模拟2018.8.15】 做运动

    5818. [NOIP提高A组模拟2018.8.15] 做运动 (File IO): input:running.in output:running.out Time Limits: 2000 ms  ...

  3. JZOJ 5812. 【NOIP提高A组模拟2018.8.14】 区间

    5812. [NOIP提高A组模拟2018.8.14] 区间 (File IO): input:range.in output:range.out Time Limits: 1000 ms  Memo ...

  4. JZOJ 4732. 【NOIP2016提高A组模拟8.23】函数

    4732. [NOIP2016提高A组模拟8.23]函数 (Standard IO) Time Limits: 1500 ms  Memory Limits: 262144 KB  Detailed ...

  5. JZOJ 5328. 【NOIP2017提高A组模拟8.22】世界线

    5328. [NOIP2017提高A组模拟8.22]世界线 (File IO): input:worldline.in output:worldline.out Time Limits: 1500 m ...

  6. JZOJ 5329. 【NOIP2017提高A组模拟8.22】时间机器

    5329. [NOIP2017提高A组模拟8.22]时间机器 (File IO): input:machine.in output:machine.out Time Limits: 2000 ms M ...

  7. JZOJ 5307. 【NOIP2017提高A组模拟8.18】偷窃 (Standard IO)

    5307. [NOIP2017提高A组模拟8.18]偷窃 (Standard IO) Time Limits: 1000 ms Memory Limits: 262144 KB Description ...

  8. JZOJ 5286. 【NOIP2017提高A组模拟8.16】花花的森林 (Standard IO)

    5286. [NOIP2017提高A组模拟8.16]花花的森林 (Standard IO) Time Limits: 1000 ms Memory Limits: 131072 KB Descript ...

  9. JZOJ 5305. 【NOIP2017提高A组模拟8.18】C (Standard IO)

    5305. [NOIP2017提高A组模拟8.18]C (Standard IO) Time Limits: 1000 ms Memory Limits: 131072 KB Description ...

  10. [JZOJ]100047. 【NOIP2017提高A组模拟7.14】基因变异

    21 世纪是生物学的世纪,以遗传与进化为代表的现代生物理论越来越多的 进入了我们的视野. 如同大家所熟知的,基因是遗传因子,它记录了生命的基本构造和性能. 因此生物进化与基因的变异息息相关,考察基因变 ...

随机推荐

  1. MIT6.828 Lab 1: C, Assembly, Tools, and Bootstrapping

    前置准备 实现机器为VMWare的虚拟机,操作系统为 Debian-11(无桌面版本),内核版本为 5.10.0,指令集为 AMD64(i7 9700K),编译器为 GCC-10 QEMU 虚拟化支持 ...

  2. whylogs工具库的工业实践!机器学习模型流程与效果监控 ⛵

    作者:韩信子@ShowMeAI 机器学习实战系列:https://www.showmeai.tech/tutorials/41 本文地址:https://www.showmeai.tech/artic ...

  3. vue3响应式原理以及ref和reactive区别还有vue2/3生命周期的对比,第二天

    前言: 前天我们学了 ref 和 reactive ,提到了响应式数据和 Proxy ,那我们今天就来了解一下,vue3 的响应式 在了解之前,先复习一下之前 vue2 的响应式原理 vue2 的响应 ...

  4. 【开发必备】单点登录,清除了cookie,页面还保持登录状态?

    背景 本地搭建了一台认证服务器.两台资源服务器,看看请求的过程 开始 没登录,直接请求资源服务器,结果跳转到的登录页面 登录后,请求了认证服务器的登录接口,然后顿重定向,最后回到了资源服务器的接口,页 ...

  5. Kafka教程(一)基础入门:基本概念、安装部署、运维监控、命令行使用

    Kafka教程(一)基础入门   1.基本概念   背景   领英->Apache   分布式.消息发布订阅系统   角色   存储系统   消息系统   流处理平台-Kafka Streami ...

  6. UVA 673 Paretheses Balance

    原题Vjudge 题目大意 怼给你一堆括号,判断是否合法 有三条规则 (1)空串合法 (2)如果\(A和B\)都合法,则\(AB\)合法(例如:\(()和[]\)都合法,则\(()[]\)合法) (3 ...

  7. css预处理器scss/sass语法以及使用

    scss scss在css基础语法上面增加了变量 (variables).嵌套 (nested rules).混合 (mixins).导入 (inline imports) 等高级功能,使用scss可 ...

  8. LOJ 数列分块入门 9 题解题报告

    LOJ 数列分块入门 9 题解题报告 \(\text{By DaiRuiChen007}\) I. 数列分块入门 1 题目大意 \(\text{Link}\) 维护一个长度为 \(n\) 的序列,支持 ...

  9. oracle创建全文索引(oracle text)

    drop table test.QQ_MsgRecord; CREATE TABLE test.QQ_MsgRecord ( msg_group VARCHAR2(200), msg_object V ...

  10. JS实现excel数据透析,形成关系图

    网上查了好多例子,都没有找到答案,只能自己硬着头皮写了 想要的样子: 下面是DEMO,已经实现效果了!!!! 举例  导入 <!DOCTYPE html> <html lang=&q ...