Typesetting math: 100%

11/29 NOIP 模拟赛

总结:

T1 升降梯上(updown),考场 AC

T2 重叠的图像(Frame Up),读错题了,惨爆 44pts

T3 小奇回地球(earth),hzwer 大佬的神仙题,完全不会做,但找不到 OJ 能测...

T4 道路(road),最小生成树板子。然而刚开始没看出来,浪费了很长时间。好在最后 AC 了

T1 升降梯上(updown)

题目描述

有一个 NN 层的塔,升降梯在每层都有一个停靠点。手柄有 MM 个控制槽,第 ii 个控制槽旁边标着一个数 CiCi,满足 C1<C2<C3<...<CMC1<C2<C3<...<CM。如果 Ci>0Ci>0,表示手柄扳动到该槽时,电梯将上升 CiCi 层;如果 Ci<0Ci<0,表示手柄扳动到该槽时,电梯将下降 CiCi 层:并且一定存在一个 Ci=0Ci=0,手柄最初就位于此槽中。注意升降梯只能在 1N1N 层之间移动

电梯每移动 11 层,需要花费 22 秒钟时间,而手柄从一个控制槽扳到相邻的槽,需要花费 11 秒钟时间。探险队员现在在 11 层,并且想尽快到达 NN 层,他们想知道 11 层到 NN 层至少需要多长时间?

输入格式

第一行两个正整数 NN, MM

第二行 MM 个整数 C1,C2,C3,...,CmC1,C2,C3,...,Cm

输出格式

输出一个整数表示答案,即至少需要多长时间。若不可能到达输出 11

数据范围

对于 30%30% 的数据,满足 1N10, 2M51N10, 2M5

对于 100%100% 的数据,满足 1N1000, 2M20, N<C1<C2<...<CM<N1N1000, 2M20, N<C1<C2<...<CM<N

Solution

由一个普通的 dijkstradijkstra 稍加更改得到。因为手柄的位置也应该关心,所以记 visvis 时也应该标记当前手柄位置。那么入队条件变成了更新最短路径手柄位置未标记。相应的,当前的步数也应该入队。

Code

copy
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> #include <cmath> #define LL long long using namespace std; const int N = 233333; LL n, m, st = 0, c[N], dis[N], vis[N], use[N][30]; struct node //dijkstra 板子重载运算符,按照 dis 从小到大排序 { LL x, dis, now; friend bool operator < (node a, node b) { return a.dis > b.dis; } }; LL dijkstra() { priority_queue <node> q; memset(use, 0, sizeof(use)); memset(dis, 0x7f7f7f, sizeof(dis)); memset(vis, 0, sizeof(vis)); q.push((node) { 1, 0, st }); dis[1] = 0, use[1][st] = 1; while(!q.empty()) { node tmp = q.top(); q.pop(); if(vis[tmp.x]) continue; vis[tmp.x] = 1; for(int i = 1; i <= m; i++) { if(c[i] == 0) continue; int len = abs(tmp.now - i) + (abs(c[i])) * 2, nxt = tmp.x + c[i]; //计算代价 if(nxt > n || nxt < 1) continue; //判断越界 if(len + tmp.dis < dis[nxt] || !use[nxt][i]) { dis[nxt] = min(dis[nxt], len + tmp.dis); use[nxt][i] = 1; if(!vis[nxt]) q.push((node) { nxt, len + tmp.dis, i }); } } } if(dis[n] == dis[0]) return -1; return dis[n]; } int main() { freopen("updown.in", "r", stdin); freopen("updown.out", "w", stdout); scanf("%lld%lld", &n, &m); for(int i = 1; i <= m; i++) { scanf("%lld", &c[i]); if(c[i] == 0) st = i; } printf("%lld\n", dijkstra()); fclose(stdin); fclose(stdout); return 0; }

T2 重叠的图像(frame)

题面都在上面的链接里了...

Solution

和容易想到暴枚 26!26!,很明显会 TLE(但也能拿很多分啊喂)

考虑加一些限制条件,形如 AA 的图像在 BB 的图像上方。这东西有什么用呢?如果满足 AABB 上面就从 BB 连一条边到 AA,那么从下往上放图像的过程中,只有放了 BB 才能放 AA.

是不是很熟悉?这就是拓扑排序的过程!每次从队列里取出一个元素,这个元素是不确定的,这就造就了此题的多解。然而麻烦就麻烦再这里。因为多解,所以需要 dfsdfs 暴力枚举所有可能情况,然后发现 stlstl 又不好用了,只能手写队列拓扑。细节贼多,都在代码里。

Code

copy
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N = 2333; char a[N][N]; int n, m, Num = 0, tot = 0, res = 0; int vis[N], lx[N], rx[N], ly[N], ry[N], e[N][N]; int q[N], du[N], use[N]; string ans[2333333]; //答案数组一定要开足够大! void dfs(int num, int stp) { if(stp == Num) { res++; ans[res] = ""; for(int i = 1; i <= stp; i++) ans[res] += (char)(use[i]); return ; } //这里不能对 q 排序,要不然 dfs 回溯就乱套了 for(int i = 1; i <= num; i++) //取第 i 个元素 { int x = q[i], cnt = num - 1; for(int j = i; j < num; j++) q[j] = q[j + 1]; for(int j = 'A'; j <= 'Z'; j++) { if(vis[j] && e[x][j]) { du[j]--; if(du[j] == 0) q[++cnt] = j; } } use[stp + 1] = x; dfs(cnt, stp + 1); //回溯 for(int j = num; j > i; j--) q[j] = q[j - 1]; q[i] = x; for(int j = 'A'; j <= 'Z'; j++) if(vis[j] && e[x][j]) du[j]++; } } int main() { scanf("%d%d", &n, &m); memset(e, 0, sizeof(e)); memset(lx, 0x3f, sizeof(lx)); memset(ly, 0x3f, sizeof(ly)); memset(vis, 0, sizeof(vis)); for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) { cin >> a[i][j]; if(a[i][j] >= 'A' && a[i][j] <= 'Z') { if(!vis[a[i][j]]) { vis[a[i][j]] = 1; Num++; } //标记每个字母对应的矩阵 lx[a[i][j]] = min(lx[a[i][j]], i); ly[a[i][j]] = min(ly[a[i][j]], j); rx[a[i][j]] = max(rx[a[i][j]], i); ry[a[i][j]] = max(ry[a[i][j]], j); } } for(int i = 'A'; i <= 'Z'; i++) //添加限制条件 { if(!vis[i]) continue; for(int j = ly[i]; j <= ry[i]; j++) { char ch1 = a[lx[i]][j], ch2 = a[rx[i]][j]; if(vis[ch1] && ch1 != i) e[i][ch1] = 1; if(vis[ch2] && ch2 != i) e[i][ch2] = 1; } for(int j = lx[i]; j <= rx[i]; j++) { char ch1 = a[j][ly[i]], ch2 = a[j][ry[i]]; if(vis[ch1] && ch1 != i) e[i][ch1] = 1; if(vis[ch2] && ch2 != i) e[i][ch2] = 1; } } memset(du, 0, sizeof(du)); for(int i = 'A'; i <= 'Z'; i++) { if(!vis[i]) continue; for(int j = 'A'; j <= 'Z'; j++) { if(!vis[j]) continue; if(e[i][j] == 1) du[j]++; } } for(int i = 'A'; i <= 'Z'; i++) { if(!vis[i]) continue; if(du[i] == 0) //入队要全!否则可能搜不出解 { tot++; q[tot] = i; } } dfs(tot, 0); sort(ans + 1, ans + res + 1); //按照字典序从小到大输出 for(int i = 1; i <= res; i++) cout << ans[i] << endl; return 0; }

T3 小奇回地球(earth)

啊这题完全不会呢...放上大佬 hzwerhzwer题解

考场代码也放不了了,因为考场上根本就没写代码 /dk

T4 道路(raod)/ 安慰奶牛

题目描述

JohnJohn 变得非常懒,他不想再继续维护供奶牛之间供通行的道路。

道路被用来连接 N(5N10000)N(5N10000) 个牧场,牧场被连续地编号为 1..N. 1..N. 每一个牧场都是一个奶牛的家。

FJFJ 计划除去 P(N1P100,000)P(N1P100,000) 条道路中尽可能多的道路, 但是还要保持牧场之间的连通性。你首先要决定那些道路是需要保留的 N1N1 条道路。第 jj 条双向道路连接了牧场 SjSjEj(1SjN, 1EjN, SjEj)Ej(1SjN, 1EjN, SjEj),而且走完它需要 Lj(0Lj1,000)Lj(0Lj1,000) 的时间。没有两个牧场是被一条以上的道路所连接。

奶牛们非常伤心,因为她们的交通系统被削减了. 你需要到每一个奶牛的住处去安慰她们. 每次 你到达第i个牧场的时候(即使你已经到过),你必须花去 Ci(1Ci1000)Ci(1Ci1000) 的时间和奶牛交谈。你每个晚上都会在同一个牧场(这是供你选择的)过夜,直到奶牛们都从悲伤中缓过神来。在早上起来和晚上回去睡觉的时候,你都需要和在你睡觉的牧场的奶牛交谈一次。这样你才能完成你的交谈任务。

假设 FarmerJohnFarmerJohn 采纳了你的建议,请计算出使所有奶牛都被安慰的最少时间。

输入格式

11 行,用空格隔开的两个整数 NNPP

2N+12N+1 行,第 i+1i+1 行包含了 11 个整数:CiCi

N+2N+P+1N+2N+P+1 行,第 N+j+1N+j+1 行包含用空格隔开的三个整数: Sj, Ej, LjSj, Ej, Lj

输出格式

11 行,一个整数,所需要的总时间(包含和在你所在的牧场的奶牛的两次谈话时间)

Solution

才开始以为是个最小生成树 + 树形dp,写完发现被 HackHack 了。仔细一看原来是个最小生成树板子题...这题真是人均切啊,不得不 orzorz 各位巨佬 %%%

除根节点外,每个点被访问的次数 == 它儿子的个数 +1+1. 根节点还会被额外访问 11 次。

稍微画几个图比较一下,可以得出每条边只会经过两次,即进子树一次,出子树一次。

综上,一条权值为 ww 的边(设其连接的两个点编号为 a, ba, b)对答案的贡献就是 w×2+Ca+Cbw×2+Ca+Cb. 直接以贡献为权值最小生成树即可。

最后还要选一个根节点加上,显然选花费最小的。

Code

copy
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #define LL long long using namespace std; const int N = 2333333; struct edge { LL a, b, c; } e[N]; LL n, m, Sum = 0, cnt = 0, ans = 1e18, c[N], f[N], head[N]; bool cmp(edge a, edge b) { return a.c < b.c; } LL find(LL x) { return f[x] == x ? x : f[x] = find(f[x]); } int main() { freopen("road.in", "r", stdin); freopen("road.out", "w", stdout); scanf("%lld%lld", &n, &m); memset(head, 0, sizeof(head)); for(int i = 1; i <= n; i++) scanf("%lld", &c[i]), f[i] = i; for(int i = 1; i <= m; i++) { scanf("%lld%lld%lld", &e[i].a, &e[i].b, &e[i].c); e[i].c = e[i].c * 2 + c[e[i].a] + c[e[i].b]; //根据分析修改权值 } sort(e + 1, e + m + 1, cmp); //最小生成树板子 for(int i = 1; i <= m; i++) { LL fa = find(e[i].a), fb = find(e[i].b); if(fa != fb) f[fa] = fb, Sum += e[i].c; } //找一个权值最小的点加上 for(int i = 1; i <= n; i++) ans = min(ans, c[i]); printf("%lld", ans + Sum); fclose(stdin); fclose(stdout); return 0; }
posted @   Nyxia  阅读(115)  评论(0编辑  收藏  举报
编辑推荐:
· 用 C# 插值字符串处理器写一个 sscanf
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· .NET Core内存结构体系(Windows环境)底层原理浅谈
· C# 深度学习:对抗生成网络(GAN)训练头像生成模型
阅读排行:
· 手把手教你更优雅的享受 DeepSeek
· AI工具推荐:领先的开源 AI 代码助手——Continue
· 探秘Transformer系列之(2)---总体架构
· V-Control:一个基于 .NET MAUI 的开箱即用的UI组件库
· 乌龟冬眠箱湿度监控系统和AI辅助建议功能的实现
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起