简要题解加心得

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

\(\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. Java lambda表达式基本使用

    代码示例:java.lambda.LambdaExpression 1 本质 lambda表达式本质上是对匿名内部类实例的一种简化写法. 1.1 案例 有以下List<Integer>对象 ...

  2. ThinkPHP6.0在phpstorm添加查询构造器和模型的代码提示

    ThinkPHP6.0升级后 使用查询构造器和模型都没有了提示 原因是tp6源码中没有添加注释 找到Model.php * @method Query where(mixed $field, stri ...

  3. 云小课|云小课教您如何选择Redis实例类型

    阅识风云是华为云信息大咖,擅长将复杂信息多元化呈现,其出品的一张图(云图说).深入浅出的博文(云小课)或短视频(云视厅)总有一款能让您快速上手华为云.更多精彩内容请单击此处. 摘要:购买Redis实例 ...

  4. USB限流,短路保护芯片IC

    USB口的输出电压一般是5V,在一些电源中,由于总电源5V是一个很大的总电源,再分别出很多路输出负载出来,例如5V10A,分成4个USB输出口,如果没加其他限流和保护的话,任意一个USB口的输出电流都 ...

  5. Amber22 安装过程miniconda报错处理

    最近在安装amber22时,使用其官方说明安装,但在安装miniconda这一步出错,致使amber22安装不成功! 报错如下: CondaFileIOError: '/home/lbm/amber2 ...

  6. cookie、session,、token,还在傻傻分不清?

    摘要:session 和 token 本质上是没有区别的,都是对用户身份的认证机制,只是他们实现的校验机制不一样而已. 本文分享自华为云社区<Session/Cookie/Token 还傻傻分不 ...

  7. 简述HashSet的扩容机制以及我们在重写equals()的时候为何会重写hashcode()

    简述HashSet的扩容机制以及我们在重写equals()的时候为何会重写hashcode()   摘要:在背面试知识点的时候存在这样一条著名的面试题:我们重写equals()的时候为什么要重写has ...

  8. VUEX state 的使用学习二

    转载请注明出处: state 提供唯一的数据资源,所有的共享的数据都要统一放到store 中的state中进行存储; 状态state用于存储所有组件的数据. 管理数据 // 初始化vuex对象 con ...

  9. Angular在用户登录后设置授权请求头headers.append('Authorization', 'token');

    方案1. 使用Angular  http import {Injectable} from '@angular/core'; import {Http, Headers} from '@angular ...

  10. 表单标签补充及css层叠样式

    表单标签补充及css层叠样式 一.表单知识点补充 1.获取用户输入的标签两大重要属性 name属性:类似于字典的键 value属性:类似于与字典的值 # form表单朝后端发送数据的时候,标签必须得有 ...