学习目录2

  • 第二期 (2022/07/17~2022/07/23)
    • Day 7:复习&测试——**树状数组**
      • `Morning`——树状数组复习测试
        • 考试“游记”
        • 题目总结
          • [T83. Count](http://222.180.160.110:1024/contest/2713/problem/1)
          • 理解与感悟
          • [T84. Friend](http://222.180.160.110:1024/contest/2713/problem/2)
          • 理解与感悟
          • [T85. Self-Number](http://222.180.160.110:1024/contest/2713/problem/3)
          • 理解与感悟
          • [T86. 简单题](http://222.180.160.110:1024/contest/2713/problem/4)
      • `Afternoon`——普及组题目练习
    • Day 8:新知——**哈希`Hash`表**
      • `Morning`——哈希`Hash`表新课学习
        • 学习笔记
          • 一、Hash函数
          • 二、哈希表
          • 三、冲突
          • 四、Hash函数的构造方法
          • 五、处理冲突的办法
      • `Morning & Afternoon`——哈希`Hash`表题目练习
        • [T99. 三个朋友](http://222.180.160.110:1024/contest/2650/problem/8)
        • 理解与感悟
    • Day 9:复习&测试——**Hash表**
      • `Morning`——Hash表复习测试
        • 考试“游记”
        • 题目总结
          • [T104. 子串查找](http://222.180.160.110:1024/contest/2724/problem/1)
          • 理解与感悟
          • [T105. friends](http://222.180.160.110:1024/contest/2724/problem/2)
          • 理解与感悟
          • [T106. Match](http://222.180.160.110:1024/contest/2724/problem/3)
          • 理解与感悟
    • Day 10:新知——**图的概念、结构和遍历**
      • `Morning`——图的概念、结构和遍历新课学习
        • 学习笔记
          • 一、定义
          • 二、种类
          • 三、无向图的术语
          • 四、有向图的术语
          • 五、图的表示
          • 六、图的遍历
      • `Morning & Afternoon`——图的概念、结构和遍历题目练习
        • [T113. 树的存储](http://222.180.160.110:1024/contest/2668/problem/1)
        • 理解与感悟
    • Day 11:新知——**最短路**
      • `Morning`——最短路新课学习(一)
        • 学习笔记
          • 一、概念
          • 二、`Floyd`算法
          • 三、`Dijkstra`算法
    • Day 12:新知——**最短路**
      • `Morning`——最短路新课学习(二)
        • 学习笔记
          • 四、`Bellman-Ford`算法
          • 五、`SPFA`算法
  • 第三期 (2022/07/25~2022/07/30)
    • Day 13:复习&测试——**最短路**
      • `Morning`——最短路复习测试
        • 考试“游记”
        • 题目总结
          • [T154. 最短路上的统计](http://222.180.160.110:1024/contest/2755/problem/1)
          • 理解与感悟
          • [T155. ことりのおやつ](http://222.180.160.110:1024/contest/2755/problem/2)
          • 理解与感悟
          • [T156. 新年好](http://222.180.160.110:1024/contest/2755/problem/3)
          • 理解与感悟
          • [T157. 算法导论](http://222.180.160.110:1024/contest/2755/problem/4)
          • 理解与感悟
    • Day 14:新知——**最小生成树**
      • `Morning`——最小生成树新课学习(一)
        • 学习笔记
          • 一、最小生成树
          • 二、`Prim`算法
          • 三、`Kruskal`算法
    • Day 15:复习&测试——**最小生成树**
      • `Morning`——最小生成树复习测试
        • 考试“游记”
        • 题目总结
          • 考试T1. 新的开始
          • 理解与感悟
          • 考试T2. 最小花费
          • 理解与感悟
          • 考试T3. 最小花费
          • 理解与感悟
上一篇 这一篇 下一篇
2022暑初二信息竞赛学习成果分享1 2022暑初二信息竞赛学习成果分享2

第二期 (2022/07/17~2022/07/23)

Day 7:复习&测试——树状数组

Morning——树状数组复习测试

考试“游记”

拿到题目,第一题太la了(这套题他还真是xun啊)

8:25开考。

看了眼第一题,我直接感慨:这是给小升初的做的吗,太水了。10 min, 8:35

第二题,乍一看,不就是一个筛质数的裸题吗?!看我用那珍藏已久的埃氏筛法,搞定这道water题! 15 min, 8:40

第三题题面好长,对于我一个急性子来说,直接放弃!思考了15 min8:55

第四题不就是一个瞎子都能看出来的树状数组题吗!轻轻松松! 15 min, 9:10

第五题,这什么题!四不像——一不像区间DP,二不像暴力枚举,三不像树状数组,四不像贪心…思考了很久! 55 min, 10:05

又回头看第三题,直接用一个暴力法,找出来,对了样例。15 min, 10:20

最后再看第五题,也没有其他办法,直接暴力×2,调了一会儿,时间又匆匆而过。40 min, 11:00

最后25 min,我似乎不再抱有任何希望了,直接touch fish

11:25 收卷。

测评开始… 结果如下。

选手 A B C D E 总分
C2024XSC249 AC 100 AC 100 WA 20 TLE 80 TLE 40 340/500

通过这次第四题的失败,我终于明白:一定要打快读快写

话说,快读快写怎么打的?

int read () {int f = -1, x = 0;//f表示符号,x表示绝对值char ch = getchar ();if (ch == '-') {//判断负号f = -1;} else {x = ch - '0';}while (1) {ch = getchar ();if (ch >= '0' && ch <= '9') {//正在输入数字x = 10 * x + ch - '0';} else {break;}}return f * x;//正负性*绝对值
}void write (int x) {if (x == '-') {//处理负号putchar ('-');x = -x;}if (x >= 10) {//大于10,递归输出十位及以上数位write (x / 10);}putchar (x % 10 + '0');
}

题目总结

T83. Count

题目描述
某次科研调查时得到了nnn个自然数,每个数均不超过150000000015000000001500000000(1.5×1091.5 \times 10^91.5×109)。已知不相同的数不超过100001000010000个,现在需要统计这些自然数各自出现的次数,并按照自然数从小到大的顺序输出统计结果。

输入格式
输入包含n+1n+1n+1行;第一行是整数nnn,表示自然数的个数;第222~n+1n+1n+1每行一个自然数。

输出格式
输出包含mmm行(mmm为nnn个自然数中不相同数的个数),按照自然数从小到大的顺序输出。每行输出两个整数,分别是自然数和该数出现的次数,其间用一个空格隔开。

样例
样例输入

8
2
4
2
4
5
100
2
100

样例输出

2 3
4 2
5 1
100 2

数据范围与提示
40%40\%40%的数据满足:1≤n≤10001 \le n \le 10001≤n≤1000

80%80\%80%的数据满足:1≤n≤500001 \le n \le 500001≤n≤50000

100%100\%100%的数据满足:1≤n≤2000001 \le n \le 2000001≤n≤200000

理解与感悟

简单的语法题。

先输入,再排序,最后统计。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;const int MAXN = 2e5 + 5;
int n, a[MAXN], cnt = 1;int main () {//  freopen ("count.in", "r", stdin);
//  freopen ("count.out", "w", stdout);scanf ("%d", &n);for (int i = 1; i <= n; i ++) {scanf ("%d", &a[i]);//输入}sort (a + 1, a + n + 1);//排序for (int i = 1; i <= n; i ++) {if (a[i + 1] == a[i]) {cnt ++;//统计并输出} else {printf ("%d %d\n", a[i], cnt);cnt = 1;}}return 0;
}
T84. Friend

题目描述
新年快到了,“神仙协会”准备搞一个聚会,已经知道现有会员NNN人,把会员从111到NNN编号,已知凡是和会长是老朋友的,那么该会员的号码肯定只能被自己的号码和111整除,否则都是会长新朋友,现在会长想知道究竟有几个新朋友?请你编程序帮会长计算出来。

输入格式
输入数据仅一个数NNN,表示会员人数。

输出格式
输出新朋友的人数。

样例
样例输入

7

样例输出

3

数据范围与提示
30%30\%30%的数据满足:1≤n≤100001 \le n \le 100001≤n≤10000

60%60\%60%的数据满足:1≤n≤5000001 \le n \le 5000001≤n≤500000

100%100\%100%的数据满足:1≤n≤20000001 \le n \le 20000001≤n≤2000000

理解与感悟

典型的筛质数题,直接输出n−质数个数n-质数个数n−质数个数即可。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;const int MAXN = 2e6 + 5;
int n, cnt;
bool flag[MAXN];int main () {//  freopen ("friend.in", "r", stdin);
//  freopen ("friend.out", "w", stdout);scanf ("%d", &n);for (int i = 2; i <= n; i ++) {if (flag[i] == 0) {cnt ++;for (int j = 2 * i; j <= n; j += i) {//埃氏筛质数法flag[j] = 1;}}}printf ("%d", n - cnt);//输出return 0;
}
T85. Self-Number

题目描述
在1949年印度数学家D. R. Daprekar发现了一类称作Self-Numbers的数。对于每一个正整数nnn,我们定义d(n)d(n)d(n)为加上它每一位数字的和。例如,d(75)=75+7+5=87d(75)=75+7+5=87d(75)=75+7+5=87。
给定任意正整数nnn作为一个起点,都能构造出一个无限递增的序列:n,d(n),d(d(n)),d(d(d(n)))n,d(n),d(d(n)),d(d(d(n)))n,d(n),d(d(n)),d(d(d(n))), . . . 例如,如果你从333333开始,下一个数是33+3+3=3933+3+3=3933+3+3=39,再下一个为39+3+9=5139+3+9=5139+3+9=51,再再下一个为51+5+1=5751+5+1=5751+5+1=57,因此你所产生的序列就像这样: 数字nnn被称作d(n)d(n)d(n)的发生器。在上面的这个序列中,333333是393939的发生器,393939是515151的发生器,515151是575757的发生器等等。有一些数有超过一个发生器,如101101101的发生器可以使919191和100100100。一个没有发生器的数被称作Self-Number。如前131313个Self-Number为1,3,5,7,9,20,31,42,53,64,75,86,97,...1,3,5,7,9,20,31,42,53,64,75,86,97,...1,3,5,7,9,20,31,42,53,64,75,86,97,...。我们将第个表示为,所以a1=1,a2=3,a3=5a_1=1,a_2=3,a_3=5a1​=1,a2​=3,a3​=5. . .

输入格式
输入包含整数N,K,s1,s2,...,sKN,K,s_1,s_2,...,s_KN,K,s1​,s2​,...,sK​,其中1≤N≤107,1≤K≤50001 \le N \le 10^7,1 \le K \le 50001≤N≤107,1≤K≤5000,以空格和换行分割。

输出格式
在第一行你需要输出一个数,这个数表示在闭区间[1,N][1, N][1,N]中Self-Number的数量。第二行必须包含以空格划分的KKK个数,表示a[s1]. . a[sk],这里保证所有的a[s1]. . a[sk]都小于N,但不一定按顺序排列(例如,如果N=100N=100N=100,sks_ksk​可以为111~131313,但不能为141414,因为a14=108>100a_{14}=108>100a14​=108>100)

样例
样例输入

100 10
1 2 3 4 5 6 7 11 12 13

样例输出

13
1 3 5 7 9 20 31 75 86 97
理解与感悟

首先按照题目要求依次枚举发生器,并将d(n)d(n)d(n)标记为1。

然后把没有标记成1的数塞进ans[]答案数组里。

最后输出相应的下标即可。

代码

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;const int MAXN = 1e7 + 105;
int n, k, s[MAXN], sn[MAXN], cnt;
bool f[MAXN];int d (int s) {int ans = s;while (s > 0) {ans += s % 10;s /= 10; }return ans;
}int main () {//  freopen ("number.in", "r", stdin);
//  freopen ("number.out", "w", stdout);scanf ("%d %d", &n, &k);for (int i = 1; i <= k; i ++) {scanf ("%d", &s[i]);}for (int i = 1; i <= n; i ++) {f[d (i)] = 1;//将每一个d[i]的flag值置为1}for (int i = 1; i <= n; i ++) {if (f[i] == 0) {sn[++ cnt] = i;//塞入sn[]数组} }int c = 1;printf ("%d\n", cnt);for (int i = 1; i <= k; i ++) {printf ("%d ", sn[s[i]]);//按照相应下标进行输出}return 0;
}
T86. 简单题

题目来源:CQOI 2006

有一个 nnn 个元素的数组,每个元素初始均为000 。有 mmm 条指令,要么让其中一段连续序列数字反转—— 000变 111,111 变 000(操作 111),要么询问某个元素的值(操作 222)。

例如当 n=20n=20n=20 时,101010 条指令如下:

操作 回答 操作后的数组
1 1 10 N/A 11111111110000000000
2 6 111 11111111110000000000
输入格式
第一行包含两个整数 ,表示数组的长度和指令的条数;
以下 行,每行的第一个数 表示操作的种类:

若 ,则接下来有两个数 ,表示区间 的每个数均反转;
若 ,则接下来只有一个数 ,表示询问的下标。
输出格式
每个操作 输出一行(非 即 ),表示每次操作 的回答。

样例
样例输入
20 10
1 1 10
2 6
2 12
1 5 12
2 6
2 15
1 6 16
1 11 17
2 12
2 6
样例输出
1
0
0
0
1
1
数据范围与提示
对于 的数据,;
对于 的数据,,保证 。

Afternoon——普及组题目练习

Day 8:新知——哈希Hash

Morning——哈希Hash表新课学习

学新课有三忌,一忌不听讲,二忌走神,三忌不记笔记。——C2024XSC249

学习笔记

一、Hash函数

指可以根据关键字直接计算出元素所在位置的函数。

二、哈希表

根据设定的哈希函数 Hash(key) 和处理冲突的方法将一组关键字映象到一个有限的连续的地址集(区间)上,并以关键字在地址集中的 “象” 作为记录在表中的存储位置,这种表便称为哈希表,这一映象过程称为哈希造表散列,所得存储位置称为哈希地址散列地址

三、冲突
  1. 定义:不同的元素占用同一个地址的情况叫做冲突。
  2. 发生冲突的因素
    (1) 装填因子α\alphaα
    装填因子是指哈希表中己存入的元素个数 nnn 与哈希表的大小 mmm 的比值,即α=nmα=\frac{n}{m}α=mn​。ααα越小,发生冲突的可能性越小,反之,发生冲突的可能性就越大。但是,ααα太小又会造成大量存贮空间的浪费,因此必须兼顾存储空间和冲突两个方面。
    (2)所构造的哈希函数
    构造好的哈希函数,使冲突尽可能的少。
    (3)解决冲突的方法
    设计有效解决冲突的方法 。.
四、Hash函数的构造方法
  1. 直接定址法
    取关键字或关键字的某个线性函数值为散列地址,即Hash(K)=KHash(K)=a * K + b(其中aaa、bbb为常数)。
    优点:以关键码 key 的某个线性函数值为哈希地址,不会产生冲突。
    缺点:要占用连续地址空间,空间效率低。

  2. 除后余数法 (常用)
    取关键字被不大于散列表表长 mmm 的数 ppp 除后所得的余数为哈希函数。即
    Hash(K)=Kmodp(p≤m)Hash(K) = K \mod p (p≤m)Hash(K)=Kmodp(p≤m)

    ps:经验得知,一般可选ppp为质数 或 不包含小于202020的质因子的合数。例如:131, 1331, 13331, ...

  3. 平方取中法
    取关键字平方后的中间几位为哈希函数。因为中间几位与数据的每一位都相关。
    例:258925892589的平方值为670292167029216702921,可以取中间的029029029为地址。

  4. 数字分析法
    选用关键字的某几位组合成哈希地址。
    选用原则应当是:各种符号在该位上出现的频率大致相同。

  5. 折叠法
    是将关键字按要求的长度分成位数相等的几段,最后一段如不够长可以短些,然后把各段重叠在一起相加并去掉进位,以所得的和作为地址。
    适用于:每一位上各符号出现概率大致相同的情况。
    具体方法:
    移位法:将各部分的最后一位对齐相加(右对齐)。
    间接叠加法:从一端向另一端沿分割界来回折叠后,最后一位对齐相加。
    例:元素427518964275189642751896,
    移位法: 427+518+96=1041427+518+96=1041427+518+96=1041
    间接叠加法: 42751896−>724+518+69=1311427 518 96 -> 724+518+69 =131142751896−>724+518+69=1311

  6. 随机数法
    选择一个随机函数,取关键字的随机函数值为它的哈希地址,即Hash (key) = random (key) 其中random为随机函数(random是C语言函数)。
    通常,当关键字长度不等时采用此法构造哈希函数较恰当。
    rand (): 取随机数,以默认种子1来生成,只要种子一样,无论何时何地生成的随机数都一样。
    srand (x): 将随机数的种子改为xxx。
    time (0): 获取当前时间,因为时间一直在变化,所以随机数的值也在变化。
    参考代码:

#include <cstdio>
#include <cstdlib>
#include <ctime>
using namespace std;int main () {srand (time (0));printf ("%d\n", rand ());return 0;
}
  1. 建立Hash ()函数通常考虑的因素
    (1)计算哈希函数所需时间(包括硬件指令的因素);
    (2)关键字的长度;
    (3)哈希表的大小;
    (4)关键字的分布情况;
    (5)记录的查找频率。
五、处理冲突的办法
  1. 开放地址法
    开放地址就是表中尚未被占用的地址,当新插入的记录所选地址已被占用时,即转而寻找其它尚开放的地址。
    (1) 线性探测法
    设散列函数 Hash (K) = K mod m (mmm为表长),若发生冲突,则沿着一个探查序列逐个探查(也就是加上一个增量),那么,第i次计算冲突的散列地址为:
    Hi=(H(K)+di)modm(di=1,2,…,m−1)H_i = (H(K)+d_i) \mod m (d_i=1,2,…,m-1) Hi​=(H(K)+di​)modm(di​=1,2,…,m−1)
    优点:只要哈希表未被填满,保证能找到一个空地址单元存放有冲突的元素;
    缺点:可能使第iii个哈希地址的同义词存入第i+1i+1i+1 个哈希地址,这样本应存入第i+1i+1i+1个哈希地
    址的元素变成了第i+2i+2i+2个哈希地址的同义词,……,因此,可能出现很多元素在相邻的哈希
    地址上“堆积”起来,大大降低了查找效率。
    (2) 二次探测法
    二次探测法对应的探查地址序列的计算公式为:
    Hi=(H(k)+di)modmH_i = ( H(k) + d_i ) \mod m Hi​=(H(k)+di​)modm
    其中di=12,−12,22,−22,…,j2,−j2(j≤m/2)d_i =1^2,-1^2,2^2,-2^2,…,j^2,-j^2 (j≤m/2)di​=12,−12,22,−22,…,j2,−j2(j≤m/2)。

  2. 链地址法
    基本思想:
    将具有相同哈希地址的记录链成一个单链表,m个哈希地址就设 m个单链表,然后用一个数组将m个单链表的表头指针存储起来,形成一个动态的结构。
    优点:插入、删除方便。
    缺点:占用存储空间多。

  3. 再哈希法
    基本思想:

Hi=RHi(key)(i=1,2,3,……,k)。H_i= RH_i(key) (i=1,2,3,……,k)。 Hi​=RHi​(key)(i=1,2,3,……,k)。

   其中,$RH_i()$ 均是不同的哈希函数,即在同义词产生地址冲突时计算另一个哈希函数地址,直到冲突不再发生。**优点**:不易产生“聚集”。**缺点**:增加了计算时间。
  1. 建立一个公共溢出区
    基本思想:
    假设哈希函数的值域为[0,m−1][0,m-1][0,m−1],则设向量HashTable[0,m−1]HashTable[0,m-1]HashTable[0,m−1]为基本表。在此基础上,再建立一个溢出表,在之后的哈希操作中,无论关键字的同义词生成怎样的哈希地址,一旦发生冲突,就将其放入溢出表中。

Morning & Afternoon——哈希Hash表题目练习

T99. 三个朋友

题目描述
本题译自 BalticOI 2014 Day1 T2「Three Friends」。

给定一个字符串 SSS,先将字符串 SSS 复制一次,得到字符串 TTT,然后在 TTT 中插入一个字符,得到字符串 UUU。

给出字符串 UUU,重新构造出字符串 SSS。

所有字符串只包含大写英文字母。

输入格式
第一行一个整数 NNN,表示字符串 UUU 的长度。

第二行一个长度为 NNN 的字符串,表示字符串 UUU。

输出格式
一行一个字符串,表示字符串 SSS。

特别地:

样例
样例输入 1

7
ABXCABC

样例输出 1

ABC

样例输入 2

6
ABCDEF

样例输出 2

NOT POSSIBLE

样例输入 3

9
ABABABABA

样例输出 3

NOT UNIQUE

数据范围与提示

子任务 分数 数据范围
1 353535 2≤N≤20012 \le N \le 20012≤N≤2001
2 656565 2≤N≤20000012 \le N \le 20000012≤N≤2000001

理解与感悟

前面的哈希模板就不说了,重点说后面的判断代码。

  • 如果插入的位置小于n2+1\frac{n}{2}+12n​+1,如图:

  • 如果插入的位置等于n2+1\frac{n}{2}+12n​+1,如图:

  • 如果插入的位置大于n2+1\frac{n}{2}+12n​+1,如图:

代码

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;const int MAXN = 2e6 + 5, Num = 31;
int n, d[MAXN], sum[MAXN], ans = 0, cut = 0;
char str[MAXN];
bool flag = 0;int main () {scanf ("%d\n", &n);gets (str + 1);//使用gets函数需调至C++11之前的编译环境if (n % 2 == 0) {//设S的长度为s,则U的长度为2 * s + 1,若为偶数直接不可能printf ("NOT POSSIBLE");return 0;}d[0] = 1;for (int i = 1; i <= n; i ++) {sum[i] = sum[i - 1] * Num + str[i];d[i] = d[i - 1] * Num;}unsigned long long x, y;for (int i = 1; i <= n; i ++) {if (i <= n / 2) {//判断i在中心的左侧x = (sum[i - 1] * d[n / 2 + 1 - i]) + (sum[n / 2 + 1] - sum[i] * d[n / 2 + 1 - i]);y = sum[n] - sum[n / 2 + 1] * d[n / 2];} else if (i == n / 2 + 1) {//i刚好在中心x = sum[n / 2];y = sum[n] - sum[n / 2 + 1] * d[n / 2];}  else {//i在中心的右侧x = sum[n / 2];y = (sum[i - 1] - sum[n / 2] * d[i - 1 - n / 2]) * d[n - i] + (sum[n] - sum[i] * d[n - i]); }if (x == y) {//如果两段相同,记录答案if (flag == 1 && ans != x) {//如果答案出现过且切法一致printf ("NOT UNIQUE");//答案不唯一return 0;}flag = 1, ans = x, cut = i;//记录答案}}if (flag == 0) {//如果1~n没有一个点可以用来切,则不可能printf ("NOT POSSIBLE");return 0;}int tot = 1;for (int i = 1; tot <= n / 2; i ++) {//输出答案if (i != cut) {printf ("%c", str[i]);tot ++;}}return 0;
}

Day 9:复习&测试——Hash表

Morning——Hash表复习测试

考试“游记”

九点整,老师发下了题目,我随便翻了翻——三个朋友,这题,有把握了

9:00开考。

看了眼第一题,这玩意不就是一个Hash的板题吗,几分钟就解决了,既然过了样例,就没多看了。10 min, 9:10

第二题,就是昨天刚讲的三个朋友原题(T99),按照昨天做题的思路,再拉通思考一遍,但人的记忆总是有限的(gm:评测没过还找理由),调了很久 35 min, 9:50

第三题说起简单,要实现便很难,我就理解题意都用了15 min,思考正解又用了15 min,再暴力写一个递归作法(听取WA声一片)20 min50 min, 10:40

第四题看了题面,思路很容易就知道了(瞬间感觉比T3还简单),用“正解思路”打了个大概,样例也过了,我再尝试了其他几个数据,都完全正确(放心了)。 45 min, 11:25

做完了所有题,预估300+,“检查”了30 min11:55

11:55 收卷。

测评开始… 结果如下。

选手 A B C D 总分
C2024XSC249 TLE 0 WA 92 WA 9 TLE 66 167/400

我惊,居然没有一道题Accepted(太耻辱了)

话说,第一题怎么直接T掉了,震惊我妈200年,翻开代码,只见那闪亮亮的Θ(n2)\Theta(n^2)Θ(n2)算法和时间限制:500ms展现在我的眼前。(…爆眼行为)

题目总结

T104. 子串查找

题目描述
这是一道模板题。

给定一个字符串 AAA 和一个字符串 BBB,求 BBB 在 AAA中的出现次数。AAA 和 BBB 中的字符均为英语大写字母或小写字母。

AAA 中不同位置出现的 BBB 可重叠。

输入格式
输入共两行,分别是字符串 AAA 和字符串 BBB。

输出格式
输出一个整数,表示 BBB 在 AAA 中的出现次数。

样例
样例输入

zyzyzyz
zyz

样例输出

3

数据范围与提示
1≤A,B1 \le A,B1≤A,B 的长度 ≤106\le 10^6≤106,AAA、BBB 仅包含大小写字母。

理解与感悟

不就是一个hash的板题么,不用多说(虽然我错了)。

只是要提前维护sum[]d[]数组,不要像我每一次都重新算一遍,这不超时才怪!

代码

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;const int MAXN = 1e6 + 5, Num = 131;
int len1, len2, val, cnt, sum[MAXN], d[MAXN];
char a[MAXN], b[MAXN];int read () {//汲取上次考试的教训int f = 1, x = 0;char ch = getchar ();if (ch == '-') {f = -1;} else {x = ch - '0';}while (1) {ch = getchar ();if (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0';} else {break;}}return f * x;
}void write (int x) {if (x < 0) {putchar ('-');x = -x;}if (x >= 10) {write (x / 10);} putchar ('0' + x % 10);
}int main () {//  freopen ("substring.in", "r", stdin);
//  freopen ("substring.out", "w", stdout);scanf ("%s\n%s", a + 1, b + 1);len1 = strlen (a + 1), len2 = strlen (b + 1);d[0] = 1;for (int i = 1; i <= len1; i ++) {//预处理a字符串的hash值sum[i] = sum[i - 1] * Num + a[i];d[i] = d[i - 1] * Num;}for (int i = 1; i <= len2; i ++) {val = val * Num + b[i];}for (int i = 1; i <= len1 - len2 + 1; i ++) {int get = sum[i + len2 - 1] - sum[i - 1] * d[len2];if (get == val) {//统计子串的个数cnt ++;}}write (cnt);return 0;
}
T105. friends

题目描述
有三个好朋友喜欢在一起玩游戏,A君写下一个字符串SSS,B君将其复制一遍得到TTT,C君在TTT的任意位置(包括首尾)插入一个字符得到UUU.现在你得到了UUU,请你找出SSS.

输入格式
第一行一个数NNN,表示UUU的长度.

第二行一个字符串UUU,保证UUU由大写字母组成

输出格式
输出一行,若SSS不存在,输出NOT POSSIBLE.若SSS不唯一,输出NOT UNIQUE.否则输出SSS.

样例
样例输入 1

7
ABXCABC

样例输出 1

ABC

样例输入 2

6
ABCDEF

样例输出 2

NOT POSSIBLE

样例输入 3

9

ABABABABA
样例输出 3

NOT UNIQUE

数据范围与提示
对于100%100\%100%的数据 2≤N≤20000012 \le N \le 20000012≤N≤2000001

理解与感悟

T99

代码

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;const int MAXN = 2e6 + 5, Num = 31;
int n, d[MAXN], sum[MAXN], ans = 0, cut = 0;
char str[MAXN];
bool flag = 0;int main () {scanf ("%d\n", &n);gets (str + 1);//使用gets函数需调至C++11之前的编译环境if (n % 2 == 0) {//设S的长度为s,则U的长度为2 * s + 1,若为偶数直接不可能printf ("NOT POSSIBLE");return 0;}d[0] = 1;for (int i = 1; i <= n; i ++) {sum[i] = sum[i - 1] * Num + str[i];d[i] = d[i - 1] * Num;}unsigned long long x, y;for (int i = 1; i <= n; i ++) {if (i <= n / 2) {//判断i在中心的左侧x = (sum[i - 1] * d[n / 2 + 1 - i]) + (sum[n / 2 + 1] - sum[i] * d[n / 2 + 1 - i]);y = sum[n] - sum[n / 2 + 1] * d[n / 2];} else if (i == n / 2 + 1) {//i刚好在中心x = sum[n / 2];y = sum[n] - sum[n / 2 + 1] * d[n / 2];}  else {//i在中心的右侧x = sum[n / 2];y = (sum[i - 1] - sum[n / 2] * d[i - 1 - n / 2]) * d[n - i] + (sum[n] - sum[i] * d[n - i]); }if (x == y) {//如果两段相同,记录答案if (flag == 1 && ans != x) {//如果答案出现过且切法一致printf ("NOT UNIQUE");//答案不唯一return 0;}flag = 1, ans = x, cut = i;//记录答案}}if (flag == 0) {//如果1~n没有一个点可以用来切,则不可能printf ("NOT POSSIBLE");return 0;}int tot = 1;for (int i = 1; tot <= n / 2; i ++) {//输出答案if (i != cut) {printf ("%c", str[i]);tot ++;}}return 0;
}
T106. Match

题目描述
给你一个由小写字母组成的字符串 sss,要你构造一个字典序最小的(认为左括号的字典序比右括号小)合法的括号序列与这个字符串匹配。

字符串和括号序列匹配定义为:首先长度必须相等,其次对于一对匹配的左括号和右括号i,ji,ji,j ,必须有 si=sjs_i=s_jsi​=sj​。

无解输出 -1

输入格式
一行一个字符串 sss。

输出格式
一行一个括号序列或者 -1

样例
样例输入1

abbaaa

样例输出1

(()())

数据范围与提示
对于 100%100\%100% 的数据,2≤∣s∣≤1052 \le |s| \le 10^52≤∣s∣≤105。

注:输入abab,输出-1

理解与感悟

首先用手写的栈来判断无解的情况,顺便记录d[]数组——每次匹配后残留的Hash值。

Day 10:新知——图的概念、结构和遍历

Morning——图的概念、结构和遍历新课学习

学新课有三忌,一忌不听讲,二忌走神,三忌不记笔记。——C2024XSC249

学习笔记

一、定义

图(graph),用来存储某些具体事物和这些事物中的联系。
图由顶点(vertex)——具体事物和边(edge)——联系组成
顶点集合为VVV,边的集合为EEE,图表示为G=(V,E)G=(V,E)G=(V,E)

二、种类
  1. 无向图:边没有指定方向的图。
  2. 有向图:边具有指定方向的图。
    注:有向图所连的边也叫做弧,一条边起点为弧头,终点为弧尾。
  3. 带权图:边上带有权值的图。
三、无向图的术语
  1. 两个顶点之间有边连接,则称两个顶点相邻。
  2. 路径:相邻顶点的序列。
  3. 圈:起点与终点重合的路径。
  4. 度:顶点连接边的条数。
  5. 树:没有圈的连通图。
  6. 森林:没有圈的非连通图。
四、有向图的术语
  1. 在有向图中,边是单向的,它们的邻接性是单向的。
  2. 有向路径:相邻顶点的序列。
  3. 有向环:一条至少含有一条边且起点和终点相同的路径。特别地,自环(见下图)
  4. 有向无环图(DAG):没有环的有向图。
  5. 度:一个顶点的出度和入度之和即为该顶点的度。
    (1) 入度:以顶点为弧尾的边的数量。
    (2) 出度:以顶点为弧头的边的数量。
五、图的表示
  1. 邻接矩阵
    对于一个有VVV个顶点的图而言,使用V×VV \times VV×V的二维矩阵表示
    Gi,j=1G_{i,j}=1Gi,j​=1,有边相连
    Gi,j=0G_{i,j}=0Gi,j​=0,无边相连
    无向图:Gi,j=Gj,i=1G_{i,j}=G_{j,i}=1Gi,j​=Gj,i​=1
    优点:可以用常数时间判断是否有边存在
    缺点:表示稀疏图时,浪费大量空间。

  2. 邻接表
    用一个不定长数组vector存储G[i]表示与i边有相连的序列。

  3. 链式前向星
    用几个数组来维护边之间的特殊关系。

六、图的遍历
  1. DFS(深度优先搜索)
  2. BFS(广度优先搜索)

Morning & Afternoon——图的概念、结构和遍历题目练习

T113. 树的存储

题目描述
告诉你一颗树有n(n≤1000)n(n \le1000)n(n≤1000)个结点,然后告诉你n个父子结点关系,请按顺序输出1...n1...n1...n个结点的父结点和子结点,根目录的父结点输出0

输入格式
第一行一个数nnn,表示树的结点数

然后n行,每行二个数ai,aja_i,a_jai​,aj​,结点aia_iai​表示孩子,结点aja_jaj​表示父亲,即aja_jaj​是aia_iai​的父亲。保证aja_jaj​为0或在前面已出现过。

输出格式
依次输出每个结点的父结点和孩子结点,每个结点输两行。

第一行表示该结点以及该结点的父亲,中间用:隔开;

第二行表示当前结点的孩子,孩子与孩子之间用空格隔开, 且孩子的编号要按从小到大排列。

对于叶结点,虽然它没有孩子,但是也要输出一个字符串nobody

样例
输入样例

7
1 0
2 1
3 1
4 2
5 3
7 4
6 2

输出样例

1:0
2 3
2:1
4 6
3:1
5
4:2
7
5:3
nobody
6:2
nobody
7:4
nobody

理解与感悟

树的板题。
用一个结构体来存储树,按照要求输入输出即可。

代码

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;const int MAXN = 1000 + 5;
int n, x, y;
struct node {int par, len, ch[MAXN];//par父节点,len孩子的个数,ch孩子
} Tree[MAXN];int main () {scanf ("%d", &n);for (int i = 1; i <= n; i ++) {scanf ("%d %d", &x, &y);Tree[x].par = y;Tree[y].ch[++ Tree[y].len] = x;}for (int i = 1; i <= n; i ++) {printf ("%d:%d\n", i, Tree[i].par);if (Tree[i].len == 0) {//无子,输出nobodyprintf ("nobody\n");continue;}sort (Tree[i].ch + 1, Tree[i].ch + Tree[i].len + 1);//要排序for (int j = 1; j <= Tree[i].len; j ++) {printf ("%d ", Tree[i].ch[j]);}printf ("\n");}return 0;
}

Day 11:新知——最短路

Morning——最短路新课学习(一)

学新课有三忌,一忌不听讲,二忌走神,三忌不记笔记。——C2024XSC249

学习笔记

一、概念

最短路径问题就是寻找图中两节点之间的最短路径。

二、Floyd算法

Floyd算法是最简单的最短路径算法,可以计算图中任意两点间的最短路径,但是时间复杂度和空间复杂度极高:Θ(n3)\Theta(n^3)Θ(n3),且适用于负边权。

  1. 初始化
    定义一个数组f[i][j],表示i点到j点的最短路。
    伪代码:
    memset(f,inf)memset(f,inf)memset(f,inf)
    fi,i=0f_{i,i}=0fi,i​=0
    有边相连:fu,v=wu,vf_{u,v}=w_{u,v}fu,v​=wu,v​
    无边相连:fu,v=inff_{u,v}=inffu,v​=inf

  2. 算法

void floyd () {for (int k = 1; k <= n; k ++) {for (int i = 1; i <= n; i ++) {for (int j = 1; j <= n; j ++) {f[i][j] = min (f[i][j], f[i][k] + f[k][j]);}}}
}
算法本质 DP
阶段 第iii个点经过前kkk个点中任意若干个点到第jjj个点
状态转移方程 fk,i,j=min⁡(fk−1,i,j,fk−1,i,k+fk−1,k,j)f_{k,i,j}=\min(f_{k-1,i,j},f_{k-1,i,k}+f_{k-1,k,j})fk,i,j​=min(fk−1,i,j​,fk−1,i,k​+fk−1,k,j​)
状态转移方程(省去kkk维) min⁡(fi,j,fi,k+fk,j)\min(f_{i,j},f_{i,k}+f_{k,j})min(fi,j​,fi,k​+fk,j​)
  1. 记录路径
    定义pre[i][j]表示从ij的路径中j的前驱。
  • 初始化:prei,j=ipre_{i,j}=iprei,j​=i(在无向图中prej,i=jpre_{j,i}=jprej,i​=j)
  • 更新:prei,j=prek,jpre_{i,j}=pre_{k,j}prei,j​=prek,j​
三、Dijkstra算法

把结点分成两组:已经确定最短路的结点,尚未确定最短路的结点。
我们不断从第2组中的结点放入第1组并扩展。
本质是贪心,只能应用于正权图
普通Dijkstra算法的时间复杂度为Θ(O2)\Theta(O^2)Θ(O2),小根堆优化后时间复杂度为Θ(log⁡n)\Theta(\log n)Θ(logn)~Θ(log⁡m)\Theta(\log m)Θ(logm)

  1. 松弛
    做一个形象的比喻,原来用一根橡皮筋直接连接aaa、bbb两点,若有一点kkk使得a→k→ba→k→ba→k→b比a→ba→ba→b更短,则改成a→k→ba→k→ba→k→b,让橡皮筋更松弛。

代码

if (dis[b] > dis[k] + w[k][b]) {dis[b] = dis[k] + w[k][b];
}
  1. 初始化
    我们设起点为sss,终点为eee,dis[v]表示从指定sss到vvv的最短路,pre[v]表示vvv的前驱,用来输出路径。
    伪代码:
    memset(dis,+∞)memset(dis,+\infty)memset(dis,+∞)
    memset(vis,0)memset(vis,0)memset(vis,0)
    for(i:1for(i:1for(i:1~n)→disi=ws,in)→dis_i=w_{s,i}n)→disi​=ws,i​
    dis0=pres=0,viss=1dis_0=pre_s=0,vis_s=1dis0​=pres​=0,viss​=1

  2. 算法

for(i:1for(i:1for(i:1~n−1)n-1)n−1)
(1) 在所有未标记的点中找出disdisdis最小的kkk
(2) 标记kkk
(3)松弛从kkk出发的边

void dijkstra () {memset (dis, 0x3f, sizeof dis);memset (vis, 0, sizeof vis);for (int i = 1; i <= n; i ++) {dis[i] = w[s][i];}dis[0] = pre[s] = 0, vis[s] = 1;for (int i = 1; i <= n - 1; i ++) {int k = -1, minn = 0x3f3f3f3f;for (int j = 1; j <= n; j ++) {if (dis[j] < minn) {k = j, minn = dis[j];}}if (vis[k] == 1) {continue;}vis[k] = 1;for (int i = 1; i <= n; i ++) {if (w[i][k] != 0X3f3f3f3f) {if (dis[i] > dis[k] + w[k][i]) {dis[i] = dis[k] + w[k][i];} }}}
}
  1. Dijkstra算法堆优化(小根堆)

(1) 找出disdisdis最小的kkk,我们可以用priority_queue优先队列来完成(优化第一个小循环)
(2) 用邻接表或链式前向星拉出与kkk相邻的边的序列(优化第二个小循环)

priority_queue <pair <int, int> > q;void dijkstra () {while (q.size()) {q.pop();}memset (dis, 0x3f, sizeof dis);dis[s] = 0;q.push(make_pair (0, s));while (q.size()) {int t = q.top().second;q.pop();if (vis[t] == 1) {continue;}vis[t] = 1;for (int i = head[t]; i; i = next[i]) {int x = to[i], y = w[i];if (dis[x] > dis[t] + y) {dis[x] = dis[t] + y;q.push(make_pair (-dis[x], x));}}}
}

Day 12:新知——最短路

Morning——最短路新课学习(二)

学新课有三忌,一忌不听讲,二忌走神,三忌不记笔记。——C2024XSC249

学习笔记

四、Bellman-Ford算法

对每条边执行更新,迭代N−1N-1N−1次。具体操作是对图进行最多n−1n-1n−1次松弛操作,每次操作对所有的边进行松弛,可以应用于有向负权图

  1. 初始化

伪代码:

memset(dis,∞),diss=0,pres=0memset(dis,\infty), dis_s=0,pre_s=0memset(dis,∞),diss​=0,pres​=0

  1. 算法
void relax (int x, int y, int w) {if (dis[y] > dis[x] + w) {dis[y] = dis[x] + w;}
}for (int i = 1; i <= n - 1; i ++) {for (int j = 1; j <= m; j ++) {relax (edge[j].s, edge[j].e, edge[j].w);}
}

在n−1n-1n−1次操作中,枚举每一条边是否能被松弛(relax操作)即可。

  1. 判断负环

(1)负环的概念:权值为负数的有向环即为负环。

(2)在Bellman_Ford算法中判断负环:

观察下图:

负环都满足以下性质:
diss+w<disedis_s+w<dis_e diss​+w<dise​
所以,我们再用一重循环,若满足这个性质,一定出现负环。

void relax (int x, int y, int w) {if (dis[y] > dis[x] + w) {dis[y] = dis[x] + w;}
}bool bellman_ford () {memset (dis, 0x3f, sizeof dis);dis[s] = 0;for (int i = 1; i <= n - 1; i ++) {for (int j = 1; j <= m; j ++) {relax (edge[j].s, edge[j].e, edge[j].w);}}for (int i = 1; i <= m; i ++) {if (dis[edge[i].s] + edge[i].w < dis[edge[i].e]) {return 0;}}return 1;
}
五、SPFA算法

SPFA算法,即用队列优化的Bellman-Ford算法,本质上还是迭代——每更新一次就考虑入队。

可以应用于有向负权图。

时间复杂度:稀疏图上Θ(kN)\Theta(kN)Θ(kN),稠密图上退化到Θ(N2)\Theta(N^2)Θ(N2)

  1. 算法实现

Bellman-Ford算法中,有许多松弛是无效的。这给了我们很大的改进的空间。SPFA算法正是对Bellman-Ford算法的改进。它是由西南交通大学段丁凡1994年提出的。它采用了队列和松弛技术。先将源点加入队列。然后从队列中取出一个点(此时该点为源点),对该点的邻接点进行松弛,如果该邻接点松弛成功且不在队列中,则把该点加入队列。如此循环往复,直到队列为空,则求出了最短路径。

  1. 判断有无负环:如果某个点进入队列的次数超过NNN次则存在负环 ( 存在负环则无最短路径,如果有负环则会无限松弛,而一个带nnn个点的图至多松弛n−1n-1n−1次)
void spfa (int s) {memset (dis, 0x3f, sizeof dis);memset (vis, 0, sizeof vis);memset (c, 0, sizeof c);//c数组来判断进入队列的次数queue <int> q;dis[s] = 0, vis[s] = 1, c[s] = 1; q.push(s);while (q.size()) {int t = q.front();q.pop();vis[t] = 0;for (int i = head[t]; i; i = next[i]) {int x = to[i], y = w[i];if (dis[x] > dis[t] + y) {dis[x] = dis[t] + y;c[x] = c[t] + 1;if (c[x] == n) {printf ("-1");exit (0);}if (vis[x] != 1) {q.push(x);vis[x] = 1;}}}}
}

第三期 (2022/07/25~2022/07/30)

Day 13:复习&测试——最短路

Morning——最短路复习测试

考试“游记”

这才叫突击测试!—— gm:好了,我准备发题了8:39

8:40开考。

看了眼第一题,这题目看起来就像板题,可是一细读——这啥题?我连思路都没有(这难道是…)30 min, 9:10

第二题,这乍一看有一点繁琐,但实现起来谁都会啊(就你看这个85 pts还装b) 35 min, 9:50

第三题,还是道重庆省选题,就这难度,你配? 30 min, 10:20

第四题,前面的题面比较长,但对于我来说也还是小菜一碟,但当我看到他的包裹会以次短用时到达——要求次短路,…(本蒟蒻表示无语)。 1 h 5 min, 11:25

眼看就要AK了,回过来做第一题,迅速调的过了样例,再自己出了几组数据,就直接放弃。20 min, 11:45

做完了所有题,简单检查了一下,直接交(面对疾风吧)。 5 min, 11:50

11:50 收卷。

测评开始… 结果如下。

选手 A B C D 总分
C2024XSC249 WA 0 WA 85 AC 100 WA 30 215/400

第一道题听完评讲后,发现我自己思路都错了,悲惨…

第二题又爆眼了,不考虑点心店和 ことり 家的雪,直接送出15 pts

第四题还骗了30 pts,爽!(虽然在OJ上我得了40 pts)

题目总结

T154. 最短路上的统计

题目描述
一个无向图上,没有自环,所有边的权值均为111,对于一个点对(a,b)(a,b)(a,b) 我们要把所有aaa与bbb之间所有最短路上的点的总个数输出。

输入格式
第一行n,mn,mn,m,表示nnn个点,mmm条边 接下来mmm行,每行两个数a,ba,ba,b,表示a,ba,ba,b之间有条边 在下来一个数ppp,表示问题的个数 接下来ppp行,每行两个数a,ba,ba,b,表示询问a,ba,ba,b, n≤100,p≤5000n \le 100,p \le 5000n≤100,p≤5000

输出格式
对于每个询问,输出一个数ccc,表示a,ba,ba,b之间最短路上点的总个数

样例
样例输入

5 6
1 2
1 3
2 3
2 4
3 5
4 5
3
2 5
5 1
2 4

样例输出

4
3
2

样例解释

对于第一个询问有2,3,4,5这四个点

理解与感悟

既然是多源最短路,就应该用Floyd来做。

我们先用三重循环跑一遍Floyd,先求得最短路。

再用三重循环来统计一下最短路所包含的结点数。

for (int k = 1; k <= n; k ++) {for (int i = 1; i <= n; i ++) {for (int j = 1; j <= n; j ++) {if (f[i][j] == f[i][k] + f[k][j]) {//若k是i~j最短路上的一个点,就统计一下k。cnt[i][j] ++;}}}
}

Question & Answer
Q:这样统计会不会出现一个点被多次统计的情况?

A:答案是不会。我们可以观察一下这个三重循环:

因为k是在最外层循环,所以对于每一个k,都只会出现一次,所以不可能出现两次同样的k。

最后就只需要输出cnt[i][j]的值即可。

代码

#include <cstdio>
#include <algorithm>
#include <cstring>
#define inf 0x3f3f3f3f
using namespace std;const int MAXN = 100 + 5;
int n, m, p, x, y, f[MAXN][MAXN], cnt[MAXN][MAXN];int read () {int f = 1, x = 0;char ch = getchar ();if (ch == '-') {f = -1;} else {x = ch - '0';} while (1) {ch = getchar ();if (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0';} else {break;}}return f * x;
}void write (int x) {if (x < 0) {x = -x;putchar ('-');}if (x >= 10) {write (x / 10);}putchar (x % 10 + '0');
}void AddEdge (int _x, int _y) {f[_x][_y] = f[_y][_x] = 1;
}void Floyd () {for (int k = 1; k <= n; k ++) {for (int i = 1; i <= n; i ++) {for (int j = 1; j <= n; j ++) {if (f[i][j] > f[i][k] + f[k][j]) {f[i][j] = f[i][k] + f[k][j];}}} }for (int k = 1; k <= n; k ++) {for (int i = 1; i <= n; i ++) {for (int j = 1; j <= n; j ++) {if (f[i][j] == f[i][k] + f[k][j]) {cnt[i][j] ++;}}} }
}int main () {//  freopen ("statistics.in", "r", stdin);
//  freopen ("statistics.out", "w", stdout);n = read (), m = read ();memset (f, 0x3f, sizeof f);for (int i = 1; i <= n; i ++) {f[i][i] = 0;} for (int i = 1; i <= m; i ++) {x = read (), y = read ();AddEdge (x, y);}Floyd ();p = read ();for (int i = 1; i <= p; i ++) {x = read (), y = read ();write (cnt[x][y]);puts (""); }return 0;
}
T155. ことりのおやつ

题目描述

这是 2017 年的冬天。(又到了白色相簿的季节 2333)
滑完雪之后, ことり 突然想吃点心啦!wtnap 收到短信时正好在甜品店,于是 wtnap 决定买好了带去给她。不幸的是,日本的冬天经常下雪,今天也不例外。

秋叶原共有 nnn 个地点,分别编号为 1,2,3,...,n1,2,3,...,n1,2,3,...,n 。点心店所在地点的编号是 sss,ことり家的编号是 ttt 。
有 mmm 条道路连接这些地点,它们的长度分别为 wiw_iwi​ m 。为了便于绘制地图,秋叶原的道路规划保证每条道路严格地连接两个不同的地点,并且不会有两条道路连接的两个地点相同。
雪太大,公共交通系统已经停摆了,所以你得走路过去。你的走路速度是 111 m/s 。
开始时,地点 iii 的积雪深度为 hih_ihi​ mm 。每秒钟地面上积雪的厚度会增加 qqq mm。每个地点都有一个步行的极限雪深 lil_ili​ mm ,如果到达此地时此地的雪深 >li> l_i>li​,wtnap 会被困在这个点,无法成功地走到 ことり 家。
不考虑点心店和 ことり 家的雪。ことり 想在 ggg 秒内吃到点心, wtnap 要越快越好。如果在 ggg 秒之内, wtnap 无法到达 ことり 的家,或者 wtnap 被困在路上了,那么 ことり 会把 wtnap 变成她的点心。

输入格式
第 111 行 666 个整数,空格隔开,分别代表 n,m,s,t,g,qn,m,s,t,g,qn,m,s,t,g,q 。
以下 nnn 行,每行 222 个整数,空格隔开,分别表示这个地点的 hih_ihi​ 和 lil_ili​ 。
以下 nnn 行,每行 333 个整数,空格隔开,分别表示这条路连接的两个地点 u,vu,vu,v 和这条路的长度 wiw_iwi​ 。

输出格式
如果 wtnap 变成了 ことり 的点心那么输出一行字符串 wtnap wa kotori no oyatsu desu!
否则输出一行一个整数,表示到达ことり家的最短用时。

样例
样例输入 1

2 1 1 2 10 1
1 10
3 10
1 2 6

样例输出 1

6

样例输入 2

5 6 2 5 10 1
1 10
1 10
1 10
1 10
1 10
1 5 9
1 3 9
2 4 1
2 5 9
3 4 1
3 5 6

样例输出 2

8

样例输入 3

5 6 2 5 10 1
1 10
1 10
10 10
1 10
1 10
1 5 9
1 3 9
2 4 1
2 5 11
3 4 1
3 5 6

样例输出 3

wtnap wa kotori no oyatsu desu!

数据范围与提示
特殊约定
没有测试数据与样例相同。
对于40%40\%40% 的数据,q=0q=0q=0 。在 q=0q=0q=0 的数据中,有 50%50\%50% 的数据 wi<li(1≤i≤n)w_i < l_i(1 \le i \le n)wi​<li​(1≤i≤n) 。
对于所有数据,1≤s,t≤n,q≤109,0≤wi<li≤1091 \le s,t \le n,q \le 10^9,0 \le w_i<l_i \le 10^91≤s,t≤n,q≤109,0≤wi​<li​≤109 。

数据范围

测试点编号 nnn mmm 其他约定
111~444 ≤10\le 10≤10 ≤20\le 20≤20 对于编号为奇数的测试点 q=0q=0q=0,hi<lih_i<l_ihi​<li​
555~888 ≤100\le 100≤100 ≤200\le 200≤200 对于编号为奇数的测试点 q=0q=0q=0,hi<lih_i<l_ihi​<li​
999~121212 ≤1000\le 1000≤1000 ≤2000\le 2000≤2000 对于编号为奇数的测试点 q=0q=0q=0
131313~161616 ≤104\le 10^4≤104 ≤2×104\le 2 \times 10^4≤2×104 对于编号为奇数的测试点 q=0q=0q=0
171717~202020 ≤105\le 10^5≤105 ≤2×105\le 2 \times 10^5≤2×105
理解与感悟

Dijkstra的板题啊喂!

这道题的坑点主要有两个:

  1. q=0q=0q=0,也就是说每秒钟增加的厚度为000——限制:除法,除0就会RE
  2. 不考虑点心店和 ことり 家的雪,也就是说就算最后一步走不到,但在家门口就可以。

只要注意这两个坑点就可以轻松AC

代码

#include <cstdio>
#include <queue>
#include <algorithm>
#include <cstring>
#define inf 0x3f3f3f3f
using namespace std;const int MAXN = 1e5 + 5, MAXM = 1e6 + 5;
int n, m, s, t, x, y, z, g, q, cnt;
int h[MAXN], l[MAXN], to[MAXM], Next[MAXM], head[MAXM], w[MAXM];
int dis[MAXN];
bool vis[MAXN];int read () {int f = 1, x = 0;char ch = getchar ();if (ch == '-') {f = -1;} else {x = ch - '0';} while (1) {ch = getchar ();if (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0';} else {break;}}return f * x;
}void write (int x) {if (x < 0) {x = -x;putchar ('-');}if (x >= 10) {write (x / 10);}putchar (x % 10 + '0');
}void AddEdge (int _x, int _y, int _z) {to[++ cnt] = _y, w[cnt] = _z, Next[cnt] = head[_x], head[_x] = cnt;
}priority_queue <pair <int, int> > p;void dijkstra () {memset (dis, 0x3f, sizeof dis);dis[s] = 0;p.push(make_pair (0, s));while (p.size()) {int c = p.top().second;p.pop();if (vis[c]) {continue;}vis[c] = 1;for (int i = head[c]; i; i = Next[i]) {int x = to[i], y = w[i];if (dis[x] > dis[c] + y && (h[x] + dis[c] * q + y <= l[x] || t == x)) {//这一步很关键,细细品析dis[x] = dis[c] + y;p.push(make_pair (-dis[x], x));}}}
}int main () {//  freopen ("dessert.in", "r", stdin);
//  freopen ("dessert.out", "w", stdout);n = read (), m = read (), s = read (), t = read (), g = read (), q = read ();for (int i = 1; i <= n; i ++) {h[i] = read (), l[i] = read ();}for (int i = 1; i <= m; i ++) {x = read (), y = read (), z = read ();AddEdge (x, y, z);AddEdge (y, x, z);}dijkstra ();if (dis[t] == inf) {puts ("wtnap wa kotori no oyatsu desu!");} else {write (dis[t]);}return 0;
}
T156. 新年好

题目描述
原题来自:CQOI 2005

重庆城里有 nnn 个车站,mmm 条双向公路连接其中的某些车站。每两个车站最多用一条公路连接,从任何一个车站出发都可以经过一条或者多条公路到达其他车站,但不同的路径需要花费的时间可能不同。在一条路径上花费的时间等于路径上所有公路需要的时间之和。

佳佳的家在车站 111,他有五个亲戚,分别住在车站 a,b,c,d,ea,b,c,d,ea,b,c,d,e。过年了,他需要从自己的家出发,拜访每个亲戚(顺序任意),给他们送去节日的祝福。怎样走,才需要最少的时间?

输入格式
第一行:n,mn,mn,m 为车站数目和公路的数目。

第二行:a,b,c,d,ea,b,c,d,ea,b,c,d,e 为五个亲戚所在车站编号。

以下 mmm 行,每行三个整数 x,y,tx,y,tx,y,t,为公路连接的两个车站编号和时间。

输出格式
输出仅一行,包含一个整数 TTT,为最少的总时间。

样例
样例输入

6 6
2 3 4 5 6
1 2 8
2 3 3
3 4 4
4 5 5
5 6 2
1 6 7

样例输出

21

数据范围与提示
对于全部数据,1≤n≤50000,1≤m≤105,1<a,b,c,d,e≤n,1≤x,y≤n,1≤t≤1001 \le n \le 50000,1 \le m \le 10^5, 1 < a,b,c,d,e \le n, 1 \le x,y \le n, 1 \le t \le 1001≤n≤50000,1≤m≤105,1<a,b,c,d,e≤n,1≤x,y≤n,1≤t≤100。

理解与感悟

也就是一道Dijkstra加上暴搜111到555的全排列。

按照暴搜所得到的答案即是最终的结果。

#include <cstdio>
#include <queue>
#include <algorithm>
#include <cstring>
#define inf 0x3f3f3f3f
using namespace std;const int MAXN = 50000 + 5, MAXM = 2e5 + 5;
int n, m, x, y, z, s, cnt, ans = inf;
int par[10], to[MAXM], next[MAXM], head[MAXM], w[MAXM], dis[MAXN], road[10][MAXN], ve[10];
bool vis[MAXN], fl[10];int read () {int f = 1, x = 0;char ch = getchar ();if (ch == '-') {f = -1;} else {x = ch - '0';} while (1) {ch = getchar ();if (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0';} else {break;}}return f * x;
}void write (int x) {if (x < 0) {x = -x;putchar ('-');}if (x >= 10) {write (x / 10);}putchar (x % 10 + '0');
}void AddEdge (int _x, int _y, int _z) {to[++ cnt] = _y, w[cnt] = _z, next[cnt] = head[_x], head[_x] = cnt;
}void save (int x) {for (int i = 1; i <= n; i ++) {road[x][i] = dis[i];}
}priority_queue <pair <int, int> > q;void dijkstra () {memset (dis, 0x3f, sizeof dis);memset (vis, 0, sizeof vis);dis[s] = 0;q.push(make_pair (0, s));while (q.size()) {int t = q.top().second;q.pop();if (vis[t]) {continue;}vis[t] = 1;for (int i = head[t]; i; i = next[i]) {int x = to[i], y = w[i];if (dis[x] >= dis[t] + y) {dis[x] = dis[t] + y;q.push(make_pair (-dis[x], x));}}}
}void check () {//验证答案是否有价值。int tot = 0; ve[0] = 0;for (int i = 1; i <= 5; i ++) {tot += road[ve[i - 1]][par[ve[i]]];}ans = min (ans, tot);
}void dfs (int step) {//暴搜if (step == 6) {check ();return;}for (int i = 1; i <= 5; i ++) {if (fl[i] == 0) {fl[i] = 1;ve[step] = i;dfs (step + 1);fl[i] = 0;}}
}int main () {//  freopen ("hny.in", "r", stdin);
//  freopen ("hny.out", "w", stdout);scanf ("%d %d", &n, &m);for (int i = 1; i <= 5; i ++) {scanf ("%d", &par[i]);}for (int i = 1; i <= m; i ++) {scanf ("%d %d %d", &x, &y, &z);AddEdge (x, y, z);AddEdge (y, x, z);}s = 1, dijkstra (), save (0);//save保存每次Dijkstra的结果for (int i = 1; i <= 5; i ++) {s = par[i], dijkstra (), save (i);}dfs (1);printf ("%d", ans);return 0;
}
T157. 算法导论

题目描述
小方作为一名 OI 初学者,在对入门 OI 迷茫之时发现了算法导论这本书。他十分高兴,认为这就是 OI 的正确入门方式,当即下单购买了这本旷世巨著。 然而不幸的是,热爱学习的他早已忘记了日期,买完才发现明天是双十一,小方并不关心自己多花了多少钱,只希望自己的书要早点到,不会因双十一 糟糕的物流速度耽搁了自己学习的进度。

于是,小方时不时就拿起手机查看物流进度,以了解自己的书送到了哪里。然而,更加不幸的是,宿管发现了他带手机的恶劣行径,当即没收了他的手机 并取消了小方的住宿资格。

小方并不关心自己的手机和住宿,他只关心自己的书什么时候到。由于没有了手机,他无法查看物流进度,但好在他是个记性极好的人,他记住了手机 没收前包裹所在地,并且他知道快递公司站点的位置和这些站点之间的路程,以及站点间双向道路的限速。

小方将站点重标号为 1...n1...n1...n ,且将他所在城市所属站点 ttt 、包裹所在站点 sss 以及站点间的道路信息告诉你,且保证包裹一定可以到达。

小方估计,由于双十一的巨大物流量,他的包裹会以次短用时到达。

小方希望知道,在快递公司最快效率配送的前提下(即忽略到站点至出站点的时间,且司机全程以最高速行驶),他的包裹还有多久送达。

输入格式
第一行,两个正整数 n,mn,mn,m ,表示站点数和道路数。

第二行,两个正整数 s,ts,ts,t,表示包裹所在站点和小方所在城市所属站点。

第 333 至 n+2n+2n+2 行,每行四个正整数 a,b,d,va,b,d,va,b,d,v ,描述每一条道路。

a,ba,ba,b 表示道路两端连接的站点编号,ddd 表示道路的长度(km),vvv 表示这条道路的限速(km/h)。

输出格式
一行一个整数,表示到包裹到达的次短时间(h)向下取整后的结果。

eg. 4.8 向下取整是 4 , 5.2 向下取整是 5

样例
输入样例1

2 1
1 2
1 2 6 3

输出样例1

6

输入样例2

5 4
1 5
1 2 5 5
2 3 9 3
3 4 16 4
4 5 21 4

输出样例2

15

数据范围与提示
对于样例一,最短路是 1− > 2 ,耗时 2 小时。

次短路是 1− > 2− > 1− > 2 ,耗时 6 小时。

对于样例二, 次短路是 1− > 2− > 1− > 2− > 3− > 4− > 5,耗时 15.25 小 时, 向下取整得 15 小时。

对于 20%20\%20% 的数据, n≤20n \le 20n≤20 , m≤50m \le 50m≤50,

对于 40%40\%40% 的数据, n≤100n \le 100n≤100, m≤200m \le 200m≤200,

对于 60%60\%60% 的数据, n≤1000n \le 1000n≤1000, m≤5000m \le 5000m≤5000,

对于 80%80\%80% 的数据, n≤20000n \le 20000n≤20000, m≤500000m \le 500000m≤500000,

对于 100%100\%100% 的数据, n≤100000n \le 100000n≤100000 , m≤500000m \le 500000m≤500000,

对于所有数据 s,t,a,b≤ns,t,a,b \le ns,t,a,b≤n , d≤200d \le 200d≤200 , v≤15v \le 15v≤15

理解与感悟

这个题一看题,就是一个求次短路的裸题了(只看你会不会求)。我都调了20 min

我们用dis[i][j]来表示s到i的最短(0)/次短路(1)。

  • 若答案比次短路还长,无需更新

  • 若答案比最短路还短,原先的最短路向后移。

  • 否则,只需更新次短路。

代码

#include <cstdio>
#include <queue>
#include <algorithm>
#include <cstring>
using namespace std;const int MAXN = 2e6 + 5, base = 360360;//需要整体乘1~15的最小公倍数360360
long long cnt, n, m, s, e, x, y, d, v, head[MAXN];//注意开long long
long long dis[MAXN][2];struct edge {long long to, next, w;edge () {}edge (int t, int n, int v) {to = t, next = n, w = v;}
} ed[MAXN];struct node {long long u, v, w;node () {}node (int _u, int _v, int _w) {u = _u, v = _v, w = _w;}bool operator < (const node x) const {return this->w > x.w;}
};void AddEdge (int _x, int _y, int _z) {ed[++ cnt] = edge (_y, head[_x], _z);head[_x] = cnt;
}priority_queue <node> q;void dijkstra () {memset (dis, 0x3f, sizeof dis);dis[s][0] = 0ll;q.push(node (s, 0, 0));while (q.size()) {node t = q.top();q.pop();if (dis[t.u][t.v] != t.w) {continue;}for (int i = head[t.u]; i; i = ed[i].next) {int x = ed[i].to, y = ed[i].w;if (dis[x][1] <= t.w + y) {//比次短路长continue;}if (dis[x][0] > t.w + y) {//比最短路短
//              dis[x][1] = dis[x][0];
//              q.push(node (x, 1, dis[x][1]));//正常解法需要这两句,数据有误需要注释dis[x][0] = t.w + y;q.push(node (x, 0, dis[x][0]));} else {//在最短路和次短路之间dis[x][1] = t.w + y;q.push(node (x, 1, dis[x][1]));}}}
}int main () {scanf ("%lld %lld %lld %lld", &n, &m, &s, &e);for (int i = 1; i <= m; i ++) {scanf ("%lld %lld %lld %lld", &x, &y, &d, &v);AddEdge (x, y, d * base / v);AddEdge (y, x, d * base / v);}dijkstra ();printf ("%lld", dis[e][1] / base);//乘了注意要除回来return 0;
}

Day 14:新知——最小生成树

Morning——最小生成树新课学习(一)

学新课有三忌,一忌不听讲,二忌走神,三忌不记笔记。——C2024XSC249

学习笔记

一、最小生成树
  1. 生成树:个点用N−1N-1N−1条边连接成一个连通块,形成的图形只可能是树,叫做生成树。因此,一个有N个点的连通图,边一定≥N−1\ge N-1≥N−1条。
  2. 最小生成树(Minimum Spanning TreesMST):求无向带权图的一棵子树,包含NNN个点,N−1N-1N−1条边,边权之和最小。
二、Prim算法

以任意一个点为基准点,节点分为两组:

(1) 在MST上到基准点的路径已经确定的点

(2) 尚未在MST中与基准点相连的点

不断从第222组中选择与第111组距离最近的点加入第111组,类似于Dijkstra算法,本质也是贪心,时间复杂度为Θ(n2)\Theta(n^{2})Θ(n2)。

  1. 总体思想:像Dijkstra一样,也使用“蓝白点”思想,白点代表已进入最小生成树的点,蓝点代表未进入最小生成树的点。以111为起点生成最小生成树,d[v]表示蓝点vvv与白点相连的最小边权,mst表示最小生成树的权值之和。
  2. 初始化

伪代码:

memset(d,∞),d1=0,mst=0memset(d,\infty), d_1=0, mst=0memset(d,∞),d1​=0,mst=0

  1. 算法

for(i:1for(i:1for(i:1~n−1)n-1)n−1)

(1) 寻找d最小的x,并将其标记。

(2) 累加答案mst += d[x];

(3) 再将与xxx相邻的点更新d[x]的值。

算法结束,mst即为最小生成树的权值之和。

  1. Prim算法堆优化(小根堆)

(1) 找出disdisdis最小的kkk,我们可以用priority_queue优先队列来完成(优化第一个小循环)
(2) 用邻接表或链式前向星拉出与kkk相邻的边的序列(优化第二个小循环)

其实跟Dijkstra算法的优化是一样一样的啦。

void prim () {for (int i = 1; i <= n; i ++) {d[i] = inf, vis[i] = 0;}d[1] = 0;q.push(make_pair (0, 1));while (q.size()) {int t = q.top().second;q.pop();if (vis[t] == 1) {continue;}vis[t] = 1;for (int i = head[t]; i; i = next[i]) {int x = to[i], y = w[i];if (vis[x] == 0 && d[x] > y) {d[x] = y;q.push(make_pair (-d[x], x));}}mst += d[t];}
}
三、Kruskal算法

利用并查集,起初每个点各自构成一个集合,所有边按照边权从小到大排序,依次扫描。

若当前扫描到的边连接两个不同的点集就合并,本质也是贪心,时间复杂度为Θ(Mlog⁡N)\Theta(M \log N)Θ(MlogN)。

Prim算法相比,没有基准点,该算法是不断选择两个距离最近的集合进行合并的过程。

  1. 初始化

cnt表示已经连的边数。

sort(ed,cmp→sort(ed,cmp→sort(ed,cmp→按照边权排序)))

mst=0,cnt=0mst=0,cnt=0mst=0,cnt=0

  1. 算法

for(i:1for(i:1for(i:1~m)m)m)

(1) 如果两个点不在同一个集合,合并,mst += 边权;

(2) 如果cnt连了nnn条边了,跳出算法。

struct node {long long u, v, w;bool operator < (const node x) const {return this->w < x.w;}
} ed[MAXN];void MakeSet () {for (int i = 1; i <= n; i ++) {father[i] = i;}
}int FindSet (int x) {if (x == father[x]) {return x;} else {return father[x] = FindSet (father[x]);}
}bool UnionSet (int x, int y, int i) {int a = FindSet (x), b = FindSet (y);if (a == b) {return 0;}father[a] = b;cnt ++;mst += ed[i].w;return 1;
}void kruskal () {sort (ed + 1, ed + m + 1);for (int i = 1; i <= m; i ++) {if (UnionSet (ed[i].u, ed[i].v, i) == 0) {continue;}if (cnt == n) {return;}}
}

Day 15:复习&测试——最小生成树

Morning——最小生成树复习测试

考试“游记”

9:00开考。

第一题,题目描述我都看了很久,一直没有理解题意,想来想去烦躁了,跳题 15 min, 9:15

第二题,输入好恶心!花了我很多时间来输入,其实算法就是个板题! 30 min, 9:45

回过头来看第一题,好像灵光乍现,一下子就打出来了。 20 min, 10:05

第三题,这题,我连思路都没有,在草稿纸上画了一堆,一点头绪都没有,加上第四题,我心都凉了。 1 h 40 min, 11:45

最后,我打算第三题打一个暴力,骗一点分。 15 min, 12:00

12:00 收卷。

测评开始… 结果如下。

选手 A B C D 总分
C2024XSC249 WA 55 WA 40 WA 0 0 215/400

这次又创新低了!居然Accepted都没有。

中午认真改了第二题代码,结果——tot没有置零… (白送60 pts!)

题目总结

考试T1. 新的开始

题目描述

发展采矿业当然首先得有矿井,小 FF 花了上次探险获得的千分之一的财富请人在岛上挖了 nnn口矿井,但他似乎忘记考虑的矿井供电问题……

为了保证电力的供应,小 FF 想到了两种办法:

  1. 在这一口矿井上建立一个发电站,费用为 vvv(发电站的输出功率可以供给任意多个矿井)。
  2. 将这口矿井与另外的已经有电力供应的矿井之间建立电网,费用为 ppp。

小 FF 希望身为「NewBe_One」计划首席工程师的你帮他想出一个保证所有矿井电力供应的最小花费。

输入格式

第一行一个整数 nnn,表示矿井总数。

第222~n+1n+1n+1 行,每行一个整数,第 iii个数 viv_ivi​表示在第 iii口矿井上建立发电站的费用。

接下来为一个 n×nn \times nn×n的矩阵ppp ,其中 pi,jp_{i,j}pi,j​表示在第iii 口矿井和第jjj 口矿井之间建立电网的费用(数据保证有pi,j=pj,ip_{i,j}=p_{j,i}pi,j​=pj,i​,且 pi,i=0p_{i,i}=0pi,i​=0)。

输出格式

输出仅一个整数,表示让所有矿井获得充足电能的最小花费。

样例

样例输入

4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0

样例输出

9

样例解释

小 FF 可以选择在 444号矿井建立发电站然后把所有矿井都不其建立电网,总花费是 3+2+2+2=93+2+2+2=93+2+2+2=9。

数据范围与提示

对于30%30\%30% 的数据:1≤n≤501 \le n \le 501≤n≤50;
对于100%100\%100% 的数据:1≤n≤300,0≤vi,pi,j≤1051 \le n \le 300, 0 \le v_i,p_{i,j} \le 10^51≤n≤300,0≤vi​,pi,j​≤105。

理解与感悟

考试时,我理解成了“只能放一个电站”,其实并不是这样的。

这样,我们就建出了样例中的图。

但是,我们要判断某个点需要建电站,所以我们可以建一个点000,则Gi,0G_{i,0}Gi,0​表示iii点建电站的费用:

这样我们再求一个最小生成树,就是答案了。

代码

#include <cstdio>
#include <queue>
#include <algorithm>
#include <cstring>
#define inf 0x3f3f3f3f
using namespace std;const int MAXN = 300 + 5, MAXM = 90000 + 5;
int n, v[MAXN], z, cnt, mst, ans, tot, father[MAXN];struct node {int u, v, w;node () {}node (int _u, int _v, int _w) {u = _u, v = _v, w = _w;}bool operator < (const node x) const {return this->w < x.w;}
} ed[MAXM];void MakeSet () {for (int i = 0; i <= n; i ++) {father[i] = i;}
}int FindSet (int x) {if (x == father[x]) {return x;} else {return father[x] = FindSet (father[x]);}
}void UnionSet (int x, int y, int z) {int a = FindSet (x), b = FindSet (y);if (a == b) {return;}father[a] = b;mst += z;tot ++;
}void kruskal () {sort (ed + 1, ed + cnt + 1);for (int i = 1; i <= cnt; i ++) {UnionSet (ed[i].u, ed[i].v, ed[i].w);if (tot == n) {return;}}
}void AddEdge (int x, int y, int z) {ed[++ cnt] = node (x, y, z);
} int read () {int f = 1, x = 0;char ch = getchar ();if (ch == '-') {f = -1;} else {x = ch - '0';}while (1) {if ((ch = getchar ()) >= '0' && ch <= '9') {x = x * 10 + ch - '0';} else {break;}}return f * x;
}void write (int x) {if (x < 0) {x = -x;putchar ('-');}if (x >= 10) {write (x / 10);}putchar (x % 10 + '0');
}int main () {//  freopen ("start.in", "r", stdin);
//  freopen ("start.out", "w", stdout);n = read ();for (int i = 1; i <= n; i ++) {scanf ("%d", &v[i]);AddEdge (0, i, v[i]);//加上虚点0}MakeSet ();for (int i = 1; i <= n; i ++) {for (int j = 1; j <= n; j ++) {scanf ("%d", &z);if (i < j) {//只用存左下角的边AddEdge (i, j, z);}}}kruskal ();write (mst);return 0;
}
考试T2. 最小花费

题目描述

有一个旅游区,旅游区有很多的景点,景点间需要开通缆车,使得任意两个景点可以互相到达。现在给出一些点间的缆车线路制造成本,两个景点之间可能有多重制造方式。问最少的花费是多少。

输入格式

输入包括多组数据,最多100100100组,最后以输入000表示结束。

对于每一组数据,第一行一个nnn,表示景区数量。

之后n−1n-1n−1行,每行第一个是一个大写字母,表示当前景区的编号,接着是kkk,表示当前景区对外可以有kkk种缆车线路制造方式,接着kkk对,每对第一个是一个字母,表示这条路线连接的景区,第二个是一个数字,表示制造成本。

输出格式

对于每一组数据,输出一行表示最小成本。

样例

样例输入

9
A 2 B 12 I 25
B 3 C 10 H 40 I 8
C 2 D 18 G 55
D 1 E 44
E 2 F 60 G 38
F 0
G 1 H 35
H 1 I 35
3
A 2 B 10 C 40
B 1 C 20
0

样例输出

216
30

数据范围与提示

1<n<271 < n < 271<n<27

0≤k≤150 \le k \le 150≤k≤15

每条线路的制造成本最大是100100100。

理解与感悟

板题。

输入字符时,需要存成整数,我们可以让ch = ch - 'A' + 1,将ch转化为整数类型。

再求最小生成树即可。

考试时tot忘记清零了——考试不检查所导致。

简单说一下考试检查要检查些什么:

  1. 文件输入输出:检查文件名是否匹配,以及stdin/stdout.in/.out文件、"r"/"w"是否匹配。
  2. 数组:数组大小开够了吗,无向图双向存边,开两倍了吗?
  3. 变量名:是否有C++14的关键字,比如prevnextrank等。
  4. 多组输入:一些用于计数、标记的变量或数组初始化了吗?(建议:多组输入题尽量写一个初始化函数init专门进行初始化)

代码

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;const int MAXN = 26 + 5, MAXM = 26 * 26 + 5;
int n, p, x, y, z, cnt, tot, father[MAXN];
long long mst;
char ch;struct edge {int u, v, w;edge () {}edge (int _u, int _v, int _w) {u = _u, v = _v, w = _w;}bool operator < (const edge x) const {return this->w < x.w;}
} ed[MAXM];void AddEdge (int x, int y, int z) {ed[++ cnt] = edge (x, y, z);
}void MakeSet () {for (int i = 1; i <= n; i ++) {father[i] = i;}
}int FindSet (int x) {if (x == father[x]) {return x;} else {return father[x] = FindSet (father[x]);}
}void UnionSet (int x, int y, int z) {int a = FindSet (x), b = FindSet (y);if (a == b) {return;}father[a] = b;mst += z;tot ++;
}void kruskal () {sort (ed + 1, ed + cnt + 1);for (int i = 1; i <= cnt; i ++) {UnionSet (ed[i].u, ed[i].v, ed[i].w);if (tot >= n) {return;}}
}int main () {freopen ("cost.in", "r", stdin);freopen ("cost.out", "w", stdout);while (~scanf ("%d", &n)) {tot = mst = cnt = 0;if (n == 0) {break;}for (int i = 1; i < n; i ++) {scanf ("\n%c %d", &ch, &p);x = ch - 'A' + 1;//字符转成整数for (int j = 1; j <= p; j ++) {scanf (" %c %d", &ch, &z);y = ch - 'A' + 1;AddEdge (x, y, z);//加边(Kruskal算法无需双向存边)}}MakeSet ();kruskal ();printf ("%lld\n", mst);}return 0;
}
考试T3. 最小花费

题目描述

在一个n×mn \times mn×m 的棋盘上要放置若干个守卫。对于nnn 行来说,每行必须恰好放置一个横向守卫;同理对于 mmm列来说,每列必须恰好放置一个纵向守卫。

每个位置放置守卫的代价是不一样的,且每个位置最多只能放置一个守卫,一个守卫不能同时兼顾行列的防御。请计算控制整个棋盘的最小代价。

输入格式

第一行包含两个正整数 n,mn,mn,m,分别表示棋盘的行数与列数。
接下来nnn 行,每行 mmm个正整数
其中第iii 行第jjj 列的数 wi,jw_{i,j}wi,j​表示在第 iii行第jjj 列放置守卫的代价。

样例

样例输入

3 4
1 3 10 8
2 1 9 2
6 7 4 6

样例输出

19

样例解释

在(1,1),(2,2),(3,1)(1,1),(2,2),(3,1)(1,1),(2,2),(3,1) 放置横向守卫,在 (2,1),(1,2),(3,3),(2,4)(2,1),(1,2),(3,3),(2,4)(2,1),(1,2),(3,3),(2,4)放置纵向守卫。

数据范围与提示

2≤n,m≤105,n×m≤105,1≤wi,j≤1092 \le n,m \le 10^5, n\times m \le 10^5, 1 \le w_{i,j} \le 10^92≤n,m≤105,n×m≤105,1≤wi,j​≤109

理解与感悟

通过维护一个基环树实现。

#include <cstdio>
#include <queue>
#include <algorithm>
#include <cstring>
#define inf 0x3f3f3f3f
using namespace std;const int MAXN = 2e5 + 5;
int n, m, z, cnt, ans, tot, father[MAXN];
long long mst;
bool vis[MAXN];struct node {int u, v, w;node () {}node (int _u, int _v, int _w) {u = _u, v = _v, w = _w;}bool operator < (const node x) const {return this->w < x.w;}
} ed[MAXN];void MakeSet () {for (int i = 1; i <= m + n; i ++) {father[i] = i;}
}int FindSet (int x) {if (x == father[x]) {return x;} else {return father[x] = FindSet (father[x]);}
}void UnionSet (int x, int y, int z) {int a = FindSet (x), b = FindSet (y);if (vis[a] && vis[b]) {return;} else if (a == b) {mst += z;vis[a] = 1;} else {mst += z;father[b] = a;vis[a] |= vis[b];}
}void kruskal () {sort (ed + 1, ed + cnt + 1);for (int i = 1; i <= cnt; i ++) {UnionSet (ed[i].u, ed[i].v, ed[i].w);if (tot == n) {return;}}
}void AddEdge (int x, int y, int z) {ed[++ cnt] = node (x, y, z);
} int read () {int f = 1, x = 0;char ch = getchar ();if (ch == '-') {f = -1;} else {x = ch - '0';}while (1) {if ((ch = getchar ()) >= '0' && ch <= '9') {x = x * 10 + ch - '0';} else {break;}}return f * x;
}void write (long long x) {if (x < 0) {x = -x;putchar ('-');}if (x >= 10) {write (x / 10);}putchar (x % 10 + '0');
}int main () {n = read (), m = read ();for (int i = 1; i <= n; i ++) {for (int j = 1; j <= m; j ++) {z = read ();AddEdge (i, j + n, z);}}MakeSet ();kruskal ();printf ("%lld", mst);return 0;
}

2022暑初二信息竞赛学习成果分享系列

上一篇 这一篇 下一篇
2022暑初二信息竞赛学习成果分享1 2022暑初二信息竞赛学习成果分享2

2022暑初二信息竞赛学习成果分享2相关推荐

  1. 2022暑初二信息竞赛学习成果分享1

    学习目录1 第一期 (2022/07/11~2022/07/16) Day 1:复习--**STL.二叉堆.二维费用背包** `Morning`--STL复习 [T1. 车站铁轨](http://22 ...

  2. 学大伟业:2019年数学竞赛学习经验分享

    学习是一个持之以恒的过程,需要不断探索.不断前行.在这路上,我认为最重要的是学习心态.每个人都不可避免地会遇到自己的学习困难,产生消极的想法.有区分度的是能否及时调整好自己,再重新投入到学习中去.身处 ...

  3. 为什么学习信息学竞赛 信息学奥赛学习规划 信息竞赛如何高效刷题

    OI 教育漫谈(一):为什么学习信息学竞赛 OI 教育漫谈(一):为什么学习信息学竞赛 [NOI2022]PV「什么是信息学精神?」 [NOI2022]PV「什么是信息学精神?」-CSDN博客 少儿 ...

  4. 【算法竞赛学习】学术前沿趋势-作者信息关联

    任务5:作者信息关联 5.1 任务说明 学习主题:作者关联(数据建模任务),对论文作者关系进行建模,统计最常出现的作者关系: 学习内容:构建作者关系图,挖掘作者关系 学习成果:论文作者知识图谱.图关系 ...

  5. 英语学习经验分享(四六级、竞赛、口语)

    英语学习经验分享(四六级.竞赛.口语) 什么时候开始下决心好好学英语的,自己又经历了哪些苦逼事儿??? Listening Speaking 英语竞赛有哪些需要参加??? 全国大学生英语竞赛 河南省翻 ...

  6. 学术交流 | InForSec 2023年网络空间安全国际学术研究成果分享及青年学者论坛

    隐私计算研习社 InForSec定于2023年4月8日-9日(周六.日)在南方科技大学举办"InForSec 2023年网络空间安全国际学术研究成果分享及青年学者论坛".本次学术活 ...

  7. 2022年双非计算机保研经历分享(浙大、北航、中科院等)

    2022年双非计算机保研经历分享(浙大.北航.中科院等) 1.个人情况 本科院校:湖北某不知名双非 专业:网络工程 排名:1/90 (专业和综测排名) 英语:CET4-536,CET6-464 竞赛: ...

  8. 2022级东南大学935计算机考研经验分享

    2022级东南大学935计算机考研经验分享 个人情况 政治 英语 数学 专业课 复试 总结 个人情况 我报考的是东南大学计算机科学与工程学院的电子信息计算机技术方向(专硕),初试总分396,排名第二. ...

  9. 港科招生 | 香港科大DBA(工商管理博士)项目介绍和科研成果分享

    香港科大DBA(工商管理博士) 项目介绍和科研成果分享 科技会减少我们对官方数据的依赖吗? 在追求卓越研究和创新思维的抱负下,香港科技大学商学院延续其 30 年的卓越历史,坚定不移地致力于学术独创性和 ...

最新文章

  1. Cloudstack介绍(一)
  2. php代码审计2全局变量和超全局变量
  3. UVA1602 Lattice Animals 网格动物
  4. 1.极限——例子_3
  5. [转载]共享软件的注册加密法
  6. 区块链+高薪,你还等什么?
  7. 华为NP课程笔记25-SDN简介
  8. 下载Bootstrap
  9. 轻松搞懂均匀分布、高斯分布、瑞利分布、莱斯分布(含MATLAB代码)
  10. java 字符串排列组合_字符串排列组合问题
  11. burp小技巧之抓单个网站包
  12. 分享一篇投稿英文期刊该如何回复审稿人的文章
  13. 网页打开慢,甚至突然打不开?图片刷新不出来?多半是DNS的问题!
  14. 臭打游戏的看过来!Windows 11 22H2 这波加强直接起飞
  15. vue html 格式化,解决在写Vue时,格式化代码 属性自动换行的问题
  16. pycharm debug raise NameError(breakpoint_type) NameError: jupyter
  17. 阿里云主机遭受DDOS攻击IP不能使用如何更换弹性公网IP
  18. 普安特友情提示:这样去抱猫,可能会让它反感
  19. vue 如何实现多页面应用(简单版)
  20. Edsger Wybe Dijkstra

热门文章

  1. 智能化运维最佳实践-自动化
  2. 搜狗校招编程题-建房子
  3. Python——字典类型理论及应用实例
  4. libvirt Java 实现远程管理虚拟机
  5. buctoj-python 2022.5.19
  6. 分区命令详解:用Fdisk命令硬盘分区
  7. 个人/团队/企业/组织申请计算机软件著作权的流程
  8. springboot+elasticsearch+bboss框架集成遇到的坑
  9. LeetCode 最热100题 最短路径和,minimum path sum
  10. SD卡CF卡U盘硬盘等磁盘属性显示为0字节怎么恢复数据