上一篇 | 这一篇 | 下一篇 |
---|---|---|
2022暑初二信息竞赛学习成果分享1 | 2022暑初二信息竞赛学习成果分享2 |
Morning
——树状数组复习测试拿到题目,第一题太la了(这套题他还真是xun啊)
8:25
开考。
看了眼第一题,我直接感慨:这是给小升初的做的吗,太水了。10 min, 8:35
第二题,乍一看,不就是一个筛质数的裸题吗?!看我用那珍藏已久的埃氏筛法,搞定这道water题! 15 min, 8:40
第三题题面好长,对于我一个急性子来说,直接放弃!思考了15 min
! 8: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');
}
题目描述
某次科研调查时得到了 n n n个自然数,每个数均不超过 1500000000 1500000000 1500000000( 1.5 × 1 0 9 1.5 \times 10^9 1.5×109)。已知不相同的数不超过 10000 10000 10000个,现在需要统计这些自然数各自出现的次数,并按照自然数从小到大的顺序输出统计结果。
输入格式
输入包含 n + 1 n+1 n+1行;第一行是整数 n n n,表示自然数的个数;第 2 2 2~ n + 1 n+1 n+1每行一个自然数。
输出格式
输出包含 m m m行( m m m为 n n n个自然数中不相同数的个数),按照自然数从小到大的顺序输出。每行输出两个整数,分别是自然数和该数出现的次数,其间用一个空格隔开。
样例
样例输入
8
2
4
2
4
5
100
2
100
样例输出
2 3
4 2
5 1
100 2
数据范围与提示
40 % 40\% 40%的数据满足: 1 ≤ n ≤ 1000 1 \le n \le 1000 1≤n≤1000
80 % 80\% 80%的数据满足: 1 ≤ n ≤ 50000 1 \le n \le 50000 1≤n≤50000
100 % 100\% 100%的数据满足: 1 ≤ n ≤ 200000 1 \le n \le 200000 1≤n≤200000
简单的语法题。
先输入,再排序,最后统计。
#include
#include
#include
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;
}
题目描述
新年快到了,“神仙协会”准备搞一个聚会,已经知道现有会员 N N N人,把会员从 1 1 1到 N N N编号,已知凡是和会长是老朋友的,那么该会员的号码肯定只能被自己的号码和 1 1 1整除,否则都是会长新朋友,现在会长想知道究竟有几个新朋友?请你编程序帮会长计算出来。
输入格式
输入数据仅一个数 N N N,表示会员人数。
输出格式
输出新朋友的人数。
样例
样例输入
7
样例输出
3
数据范围与提示
30 % 30\% 30%的数据满足: 1 ≤ n ≤ 10000 1 \le n \le 10000 1≤n≤10000
60 % 60\% 60%的数据满足: 1 ≤ n ≤ 500000 1 \le n \le 500000 1≤n≤500000
100 % 100\% 100%的数据满足: 1 ≤ n ≤ 2000000 1 \le n \le 2000000 1≤n≤2000000
典型的筛质数题,直接输出 n − 质数个数 n-质数个数 n−质数个数即可。
#include
#include
#include
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;
}
题目描述
在1949年印度数学家D. R. Daprekar发现了一类称作Self-Numbers的数。对于每一个正整数 n n n,我们定义 d ( n ) d(n) d(n)为加上它每一位数字的和。例如, d ( 75 ) = 75 + 7 + 5 = 87 d(75)=75+7+5=87 d(75)=75+7+5=87。
给定任意正整数 n n n作为一个起点,都能构造出一个无限递增的序列: 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))), . . . 例如,如果你从 33 33 33开始,下一个数是 33 + 3 + 3 = 39 33+3+3=39 33+3+3=39,再下一个为 39 + 3 + 9 = 51 39+3+9=51 39+3+9=51,再再下一个为 51 + 5 + 1 = 57 51+5+1=57 51+5+1=57,因此你所产生的序列就像这样: 数字 n n n被称作 d ( n ) d(n) d(n)的发生器。在上面的这个序列中, 33 33 33是 39 39 39的发生器, 39 39 39是 51 51 51的发生器, 51 51 51是 57 57 57的发生器等等。有一些数有超过一个发生器,如 101 101 101的发生器可以使 91 91 91和 100 100 100。一个没有发生器的数被称作Self-Number。如前 13 13 13个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,...。我们将第个表示为,所以 a 1 = 1 , a 2 = 3 , a 3 = 5 a_1=1,a_2=3,a_3=5 a1=1,a2=3,a3=5. . .
输入格式
输入包含整数 N , K , s 1 , s 2 , . . . , s K N,K,s_1,s_2,...,s_K N,K,s1,s2,...,sK,其中 1 ≤ N ≤ 1 0 7 , 1 ≤ K ≤ 5000 1 \le N \le 10^7,1 \le K \le 5000 1≤N≤107,1≤K≤5000,以空格和换行分割。
输出格式
在第一行你需要输出一个数,这个数表示在闭区间 [ 1 , N ] [1, N] [1,N]中Self-Number的数量。第二行必须包含以空格划分的 K K K个数,表示a[s1]. . a[sk],这里保证所有的a[s1]. . a[sk]都小于N,但不一定按顺序排列(例如,如果 N = 100 N=100 N=100, s k s_k sk可以为 1 1 1~ 13 13 13,但不能为 14 14 14,因为 a 14 = 108 > 100 a_{14}=108>100 a14=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
#include
#include
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;
}
题目来源:CQOI 2006
有一个 n n n 个元素的数组,每个元素初始均为 0 0 0 。有 m m m 条指令,要么让其中一段连续序列数字反转—— 0 0 0变 1 1 1, 1 1 1 变 0 0 0(操作 1 1 1),要么询问某个元素的值(操作 2 2 2)。
例如当 n = 20 n=20 n=20 时, 10 10 10 条指令如下:
操作 | 回答 | 操作后的数组 |
---|---|---|
1 1 10 |
N/A | 11111111110000000000 |
2 6 |
1 1 1 | 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
——普及组题目练习Hash
表Morning
——哈希Hash
表新课学习学新课有三忌,一忌不听讲,二忌走神,三忌不记笔记。——C2024XSC249
指可以根据关键字直接计算出元素所在位置的函数。
根据设定的哈希函数 Hash(key)
和处理冲突的方法将一组关键字映象到一个有限的连续的地址集(区间)上,并以关键字在地址集中的 “象” 作为记录在表中的存储位置,这种表便称为哈希表,这一映象过程称为哈希造表或散列,所得存储位置称为哈希地址或散列地址。
直接定址法
取关键字或关键字的某个线性函数值为散列地址,即Hash(K)=K
或 Hash(K)=a * K + b
(其中 a a a、 b b b为常数)。
优点:以关键码 key
的某个线性函数值为哈希地址,不会产生冲突。
缺点:要占用连续地址空间,空间效率低。
除后余数法 (常用)
取关键字被不大于散列表表长 m m m 的数 p p p 除后所得的余数为哈希函数。即
H a s h ( K ) = K m o d p ( p ≤ m ) Hash(K) = K \mod p (p≤m) Hash(K)=Kmodp(p≤m)
ps:经验得知,一般可选 p p p为质数 或 不包含小于 20 20 20的质因子的合数。例如:131, 1331, 13331, ...
平方取中法
取关键字平方后的中间几位为哈希函数。因为中间几位与数据的每一位都相关。
例: 2589 2589 2589的平方值为 6702921 6702921 6702921,可以取中间的 029 029 029为地址。
数字分析法
选用关键字的某几位组合成哈希地址。
选用原则应当是:各种符号在该位上出现的频率大致相同。
折叠法
是将关键字按要求的长度分成位数相等的几段,最后一段如不够长可以短些,然后把各段重叠在一起相加并去掉进位,以所得的和作为地址。
适用于:每一位上各符号出现概率大致相同的情况。
具体方法:
移位法:将各部分的最后一位对齐相加(右对齐)。
间接叠加法:从一端向另一端沿分割界来回折叠后,最后一位对齐相加。
例:元素 42751896 42751896 42751896,
移位法: 427 + 518 + 96 = 1041 427+518+96=1041 427+518+96=1041
间接叠加法: 42751896 − > 724 + 518 + 69 = 1311 427 518 96 -> 724+518+69 =1311 42751896−>724+518+69=1311
随机数法
选择一个随机函数,取关键字的随机函数值为它的哈希地址,即Hash (key) = random (key)
其中random
为随机函数(random是C语言函数)。
通常,当关键字长度不等时采用此法构造哈希函数较恰当。
rand ()
: 取随机数,以默认种子1来生成,只要种子一样,无论何时何地生成的随机数都一样。
srand (x)
: 将随机数的种子改为 x x x。
time (0)
: 获取当前时间,因为时间一直在变化,所以随机数的值也在变化。
参考代码:
#include
#include
#include
using namespace std;
int main () {
srand (time (0));
printf ("%d\n", rand ());
return 0;
}
Hash ()
函数通常考虑的因素开放地址法
开放地址就是表中尚未被占用的地址,当新插入的记录所选地址已被占用时,即转而寻找其它尚开放的地址。
(1) 线性探测法
设散列函数 Hash (K) = K mod m
( m m m为表长),若发生冲突,则沿着一个探查序列逐个探查(也就是加上一个增量),那么,第i次计算冲突的散列地址为:
H i = ( H ( K ) + d i ) m o d m ( d i = 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)
优点:只要哈希表未被填满,保证能找到一个空地址单元存放有冲突的元素;
缺点:可能使第 i i i个哈希地址的同义词存入第 i + 1 i+1 i+1 个哈希地址,这样本应存入第 i + 1 i+1 i+1个哈希地
址的元素变成了第 i + 2 i+2 i+2个哈希地址的同义词,……,因此,可能出现很多元素在相邻的哈希
地址上“堆积”起来,大大降低了查找效率。
(2) 二次探测法
二次探测法对应的探查地址序列的计算公式为:
H i = ( H ( k ) + d i ) m o d m H_i = ( H(k) + d_i ) \mod m Hi=(H(k)+di)modm
其中 d i = 1 2 , − 1 2 , 2 2 , − 2 2 , … , j 2 , − j 2 ( 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)。
链地址法
基本思想:
将具有相同哈希地址的记录链成一个单链表,m个哈希地址就设 m个单链表,然后用一个数组将m个单链表的表头指针存储起来,形成一个动态的结构。
优点:插入、删除方便。
缺点:占用存储空间多。
再哈希法
基本思想:
H i = R H i ( k e y ) ( 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()$ 均是不同的哈希函数,即在同义词产生地址冲突时计算另一个哈希函数地址,直到冲突不再发生。
**优点**:不易产生“聚集”。
**缺点**:增加了计算时间。