赞
踩
本狗学习算法也有一段时间了,不得不说,acwing的算法课讲的非常好,很对我的胃口,有一种相见恨晚的赶脚,如果能在刚上大学的时候遇见就好了!那我现在肯定变成大佬了(哭
还是忍不住想说一句:yxc牛逼!!!
基础算法的算法思路和模板代码学的都七七八八了,都基本敲了一遍(还剩动归和贪心的章节没学完)
确实像孔哥(我大学室友)说的,光看视频课,光自己刷题,是不行的,还是得刷周赛,有时间的限制和一种比较正式的仪式感。故本狗准备从这周开始,跟随孔哥步伐,开始参加算法周赛!并每周要把算法题目消化并整理笔记,以便回看和巩固。
本周共参加两场周赛,Acwing 和 LeetCode。
本篇是 对 acwing 本周周赛题目的笔记整理,如下
有 n 个怪兽等待你去消灭。
怪兽共分为两种形态,不妨用 0 和 1 来表示。
消灭一个 0 形态的怪兽需要耗费的法力值为 a。
消灭一个 1 形态的怪兽需要耗费的法力值为 b。
你还可以使用改造魔法将 0 形态怪兽改造为 1 形态或将 1 形态怪兽改造为 0 形态。
改造一个怪兽需要耗费的法力值为 c。
请问,将怪兽全部消灭最少需要耗费多少法力值。
题解
签到题,不考察任何算法,直接模拟即可。对于0,要么直接消灭(消耗a),要么转变成1之后再消灭(消耗c+b)。消灭一个0形态的怪兽的最小代价就是min(a,c+b)
,同理,消灭一个1形态的怪兽的最小代价是min(b,c+a)
#include<iostream> #include<cstring> #include<algorithm> using namespace std; int get_min(int n, int a, int b, int c, string s) { int m_0 = min(a, c + b); int m_1 = min(b, c + a); int res = 0; for(int i = 0; i < s.size(); i++) { if(s[i] == '0') res += m_0; else res += m_1; } return res; } int main() { int m; scanf("%d", &m); while(m--) { int n, a, b, c; scanf("%d%d%d%d", &n, &a, &b, &c); string s; cin >> s; printf("%d\n", get_min(n, a, b, c, s)); } return 0; }
给定 n n n 个石子,编号为 1 1 1 ∼ n n n。
其中第 i i i 个石子的价值为 a i a_i ai。
你需要从中任意挑选若干个石子,并将挑选好的石子按照编号从小到大的顺序排成一排。
选中的石子在排好序后需要满足,对于任意两个相邻的石子(不妨设它们的编号为 x x x, y y y ), x − y = a x − a y x-y=a_x-a_y x−y=ax−ay 均成立。
例如,当有 n = 8 n=8 n=8 个石子,石子价值分别为 [ 3 , 4 , 4 , 6 , 6 , 7 , 8 , 9 ] [3,4,4,6,6,7,8,9] [3,4,4,6,6,7,8,9],一些合理的选择方案如下:
你的选择方案不仅需要合理,而且还要使得选中石子的价值总和尽可能大。
请计算并输出价值总和的最大可能值。
题解
我的解法:DFS暴搜
貌似能求出正确答案,但是会报 Time Limit Exceeded
#include<iostream> using namespace std; typedef long long LL; const int N = 1e6; int a[N], n; LL res = 0, ans = 0; int path[N], ctn; //存储已经纳入的点 // 每次枚举第x编号的石子, 都有2种选择, 是否纳入 void dfs(int x) { if(x > n) return; // 深搜结束 // 不纳入, 直接深搜下一个节点 dfs(x + 1); bool flag = false; // 尝试纳入, 检查该次纳入是否合法 // 当前已存在纳入的石子 if(ctn > 0) { int pre = path[ctn - 1]; // 获取当前最后一个石子 if(x - pre == a[x] - a[pre]) { // 符合纳入条件 path[ctn++] = x; res += a[x]; flag = true; } else return; // 不符合纳入条件, 直接剪枝 } else { // 当前已纳入的石子为0, 直接纳入 path[ctn++] = x; res += a[x]; flag = true; } // 纳入后记录答案 ans = max(ans, res); dfs(x + 1); // 深搜下一个位置 // 深搜完毕后, 恢复现场 if(flag) { ctn--; res -= a[x]; } } int main() { scanf("%d", &n); for(int i = 1; i <= n; i++) scanf("%d", &a[i]); dfs(1); printf("%lld", ans); return 0; }
标准解法:哈希
将 x − y = a x − a y x-y=a_x-a_y x−y=ax−ay 进行一下变换,得 a x − x = a y − y a_x-x=a_y-y ax−x=ay−y,这样以来,对于 i ∈ [ 1 , n ] i \in [1,n] i∈[1,n] ,每个石子 i i i 都可以计算出一个额外的属性 a i − i a_i-i ai−i,不妨令这个属性为 s i s_i si,则所有 s i s_i si 相同的石子,能够构成一种方案。则只需要对 s i s_i si 的每种取值,求解一下其中包含的石子的总价值即可。
#include<iostream> #include<unordered_map> using namespace std; typedef long long LL; const int N = 1e6; int a[N]; int main() { unordered_map<int,LL> res; int n; scanf("%d", &n); LL ans = 0; for(int i = 1; i <= n; i++) { scanf("%d", &a[i]); res[a[i] - i] += a[i]; // 对于 a[i] - i 这一组的石子,进行价值累加 ans = max(ans, res[a[i] - i]); // 实时保存当前价值最大的组的价值总和 } printf("%lld", ans); return 0; }
给定一个 n n n 个点 m m m 条边的有向强连通图。
点的编号为 1 1 1 ∼ n n n ,边的长度均为 1 1 1。
给定一条由点 s s s 到点 t t t 的简单路径 p 1 , p 2 , . . . , p k p_1,p_2,...,p_k p1,p2,...,pk,其中 p 1 = s p_1=s p1=s, p k = t p_k=t pk=t。
注意,这条路经不一定是从点 s s s 到点 t t t 的最短路径。
现在,小明要沿着这条路径从点 s s s 走到点 t t t。
在他的行进过程中,手机上的导航软件将持续为他导航,持续为他提供最短行进线路建议。
当然,他并不一定会采纳这些建议,因为他一定会沿着之前给定的线路行进。
设想一下,在行进中,导航软件的工作过程。
首先,在点 s s s 处,导航软件会找到并显示出一条从点 s s s 到点 t t t 的最短路径。
如果小明的行进线路恰好与软件推荐线路一致,则软件推荐线路将不会发生任何改变。
但是,如果小明在某一点处,行进线路与软件推荐线路发生了分歧,例如,软件推荐前往点 v v v,小明却前往了点 w w w。
那么,在他到达点 w w w 后,软件就会实时更新推荐线路,即找到并显示出一条从点 w w w 到点 t t t 的最短路径。
导航软件会一直工作到小明到达点 t t t 为止,在这一过程中,软件的提供线路可能会经过若干次更新。
例如,给定一个有向强连通图,如下所示:
给出的简单路径为 [ 1 , 2 , 3 , 4 ] ( s = 1 , t = 4 ) [1,2,3,4](s=1,t=4) [1,2,3,4](s=1,t=4)
那么,小明从点 1 1 1 出发,导航软件找到并显示出一条从点 1 1 1 到点 4 4 4 的最短路径,这样的路径只有一条 [ 1 , 5 , 4 ] [1,5,4] [1,5,4]。
小明并未听从软件的建议,坚持到达了点 2 2 2,此时软件推荐线路实时更新,提供出一条点 2 2 2 到点 4 4 4 的最短路径,例如 [ 2 , 6 , 4 ] [2,6,4] [2,6,4]
(注意,软件提供的最短路径也有可能是 [ 2 , 3 , 4 ] [2,3,4] [2,3,4])。
小明还是不听软件的建议,坚持到达了点 3 3 3,此时软件推荐线路再次更新,提供出一条点 3 3 3 到点 4 4 4 的最短路径,即 [ 3 , 4 ] [3,4] [3,4]。
最后,小明沿软件提供路线,到达目的地点 4 4 4,软件完成导航。
总的来看,软件推荐线路发生了两次更新。
值得注意的是,如果软件在第一次更新推荐线路时,给出的最短路径为 [ 2 , 3 , 4 ] [2,3,4] [2,3,4],则小明将按照推荐线路走到终点,软件将无需再次更新推荐线路。
也就是说,由于软件在推荐最短路径时具有随机性,所以在整个行进过程中,软件更新推荐线路的次数并不确定。
现在,给定有向图和行进路线,请你求出软件更新推荐线路的最小可能次数和最大可能次数。
题解
根据题目,小明是一定会按照给出的路线
p
1
,
p
2
,
.
.
.
,
p
k
p_1,p_2,...,p_k
p1,p2,...,pk 走下去的,关键就是求解出在途中的每个点(
p
1
,
p
2
,
.
.
.
,
p
k
−
1
p_1,p_2,...,p_{k-1}
p1,p2,...,pk−1),到终点
p
k
p_k
pk ,有多少条最短路。
我们考虑小明走一步的情况,即从
p
i
p_i
pi 走到
p
i
+
1
p_{i+1}
pi+1 时,导航路线是否需要更新。
当 p i p_i pi 到 p i + 1 p_{i+1} pi+1 这条边,不在 p i p_i pi 到终点 p k p_k pk 的某一条最短路径上时
小明走到 p i + 1 p_{i+1} pi+1 时,导航路线一定会更新,所以更新路线的最小可能次数和最大可能次数都得加1。
即,导航推荐的所有可能路线中,一定不包含 p i p_i pi 到 p i + 1 p_{i+1} pi+1 这条边,则小明走到 p i + 1 p_{i+1} pi+1 时,路线一定要发生更新。
当 p i p_i pi 到 p i + 1 p_{i+1} pi+1 这条边,在 p i p_i pi 到终点 p k p_k pk 的某一条最短路径上时,此时要分情况讨论
当 p i p_i pi 到 p k p_k pk 的最短路径只有一条时
这条最短路径一定是经过 p i + 1 p_{i+1} pi+1 这个点的。即,小明位于 p i p_i pi 时,导航推荐的最短路线只有一种,那就是经过 p i + 1 p_{i+1} pi+1 的这条路线,那么小明走到 p i + 1 p_{i+1} pi+1 时,路线一定不会更新。
当 p i p_i pi 到 p k p_k pk 的最短路径多于一条时
在小明位于 p i p_i pi 时,若导航推荐的路线恰好经过 p i + 1 p_{i+1} pi+1 ,则无需更新;若导航推荐的路线不是经过 p i + 1 p_{i+1} pi+1 的,则需要更新。即,此时最小可能次数不变,最大可能次数加1。
根据上面的讨论,我们知道了,关键在于,对于某个点 p i p_i pi ,我们需要维护 p i p_i pi 到 p k p_k pk 的最短路的个数。
考虑用BFS来做,但是如果我们建一个正向图,即从点 p 1 p_1 p1 开始进行BFS,则当扩展到 p i p_i pi 时,只能得到 p 1 p_1 p1 到 p i p_i pi 的最短路,也只能得到 p 1 p_1 p1 到 p i p_i pi 的最短路个数。
求最短路的问题中,对于距离数组,我们是根据三角不等式来更新的 : d j > d i + 1 d_j \gt d_i + 1 dj>di+1。其中 i i i 是从当前队列中拿出来的,与起点距离最短的点 p i p_i pi,而 p j p_j pj 是 p i p_i pi 的相邻点。我们可以稍微变换一下,采用 d j ≥ d i + 1 d_j \ge d_i+1 dj≥di+1 来进行更新,则每次遇到满足 d j ≥ d i + 1 d_j \ge d_i+1 dj≥di+1 ,需要更新 d j d_j dj 时,我们认为找到了一条 p 1 p_1 p1 号点到 p j p_j pj 号点的最短路。对于同一个 j j j,若多次遇到了 d j ≥ d i + 1 d_j \ge d_i+1 dj≥di+1 条件,则说明 点 p 1 p_1 p1 到 点 p j p_j pj 的最短路有多条。
于是我们可以在每次遇到满足 d j ≥ d i + 1 d_j \ge d_i+1 dj≥di+1 时,都对 j j j 进行计数,计数表示的是 p 1 p_1 p1 号点到 p j p_j pj 号点的最短路的个数。(我自己的代码中考虑了重边的情况,即当存在重边时,不应当重复计数)
如果从 p 1 p_1 p1 点开始BFS,则只能得到某个点 p j p_j pj 到点 p 1 p_1 p1 的最短路径的个数。而我们这道题目,需要用到的是某个点 p j p_j pj 到终点 p k p_k pk 的最短路径的个数。所以我们考虑建反向图(建反向图的意思是,当输入数据表明存在一条边 a → b a \rightarrow b a→b 时 ,我们创建一条边 b → a b \rightarrow a b→a ),方便从终点 p k p_k pk 开始进行BFS。
由于当BFS执行完毕后,对于最短路径上的每个点 p i p_i pi 和点 p j p_j pj(假设 p i p_i pi 在 p j p_j pj 前面),都一定满足三角不等式 d i ≥ d j + 1 d_i \ge d_j+1 di≥dj+1
所以对于小明从 p i p_i pi 走到 p i + 1 p_{i+1} pi+1 这一步,我们可以通过三角不等式来判断, p i p_i pi 到 p i + 1 p_{i+1} pi+1 这条边,是否在最短路上
如此,我们的思路就比较清晰了,关键点无非就是
我的解法:
#include<iostream> #include<cstring> using namespace std; const int N = 2e5 + 10, M = N; const int INF = 0x3f3f3f3f; int h[N], e[N], ne[N], idx; // 图的邻接表存储 int ctn[N]; // 存储某个点 i 到终点 k 的最短路径的个数 int n, m, k; int path[N]; // 给定的路线 int d[N]; int q[N]; // 队列 bool st[N]; // 用于防止重边时多次对 ctn 进行计数 bool stt[N]; // 判断一个节点是否在队列q中 void add(int a, int b) { // 邻接链表, 头插法 e[idx] = b; ne[idx] = h[a]; h[a] = idx++; } void bfs() { memset(d, 0x3f, sizeof d); d[path[k]] = 0; stt[path[k]] = true; // stt表示是否添加到队列 int hh = 0, tt = -1; q[++tt] = path[k]; // 入队 while(tt >= hh) { // 当队列非空 int t = q[hh++]; // 出队 stt[t] = false; memset(st, false, sizeof st); // 用于防止重边, 添加多个 ctn // 遍历所有出边 for(int i = h[t]; i != -1; i = ne[i]) { int j = e[i]; if(d[j] >= d[t] + 1) { // 发生更新 d[j] = d[t] + 1; if(!st[j]) { // j 点没有被走过时, 添加ctn (防止重边添加多次 ctn) ctn[j]++; st[j] = true; } if(!stt[j]) { q[++tt] = j; // 入队 stt[j] = true; } } } } } int main() { memset(h, -1, sizeof h); scanf("%d%d", &n, &m); while(m--) { int a, b; scanf("%d%d", &a, &b); add(b, a); // 建反向边 } scanf("%d", &k); for(int i = 1; i <= k; i++) scanf("%d", &path[i]); bfs(); // 先进行宽搜, 建立数据 int min_c = 0, max_c = 0; // 更新路线的最小可能次数和最大可能次数 // 进行处理 for(int i = 1; i < k; i++) { int a = path[i], b = path[i + 1]; // 查看 i 到 i + 1 这条边是否在最短路上 // 由于建的是反向图, BFS从 k 开始, 所以BFS时, 点 i + 1 在 i 的前面被搜到 if(d[a] < d[b] + 1) { // 说明 i 到 i + 1 不在最短路上, 路线一定会发生更新 min_c++; max_c++; } else { // i 到 i + 1 在最短路上, 看一下最短路有多少条 if(ctn[a] > 1) { // i 点 到 k 点 的最短路个数大于1, 即不止当前这个 i 到 i + 1 这条最短路 max_c++; } } } printf("%d %d\n", min_c, max_c); return 0; }
yxc标准解法:
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 200010, M = N; int n, m; int h[N], e[M], ne[M], idx; int dist[N], cnt[N], q[N]; int path[N]; void add(int a, int b) // 添加一条边a->b { e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ; } void bfs(int start) { int hh = 0, tt = 0; memset(dist, 0x3f, sizeof dist); dist[start] = 0; q[0] = start; while (hh <= tt) { int t = q[hh ++ ]; for (int i = h[t]; ~i; i = ne[i]) { int j = e[i]; if (dist[j] > dist[t] + 1) { dist[j] = dist[t] + 1; cnt[j] = 1; q[ ++ tt] = j; } else if (dist[j] == dist[t] + 1) cnt[j] ++ ; } } } int main() { scanf("%d%d", &n, &m); memset(h, -1, sizeof h); while (m -- ) { int a, b; scanf("%d%d", &a, &b); add(b, a); } int k; scanf("%d", &k); for (int i = 1; i <= k; i ++ ) scanf("%d", &path[i]); bfs(path[k]); int minc = 0, maxc = 0; for (int i = 1; i < k; i ++ ) { int a = path[i], b = path[i + 1]; if (dist[a] < dist[b] + 1) minc ++, maxc ++ ; else if (cnt[a] > 1) maxc ++ ; } printf("%d %d\n", minc, maxc); return 0; }
本周是第一次参加周赛,战况不是很理想。Acwing 一共3道题,只做出第一道,第二题没有想到用哈希。
LeetCode 一共4道,只做出前2道,并且LeetCode的后两道还未消化完毕,笔记仍在整理中…
(完)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。