洛谷刷题笔记 其一 fufhaha 2025-02-09 2025-04-06 洛谷算法刷题整集2025.2.9 1.位运算 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <stdio.h> int main () { unsigned int a=5 ; unsigned int b=3 ; printf ("a & b = %u\n" , a & b); printf ("a | b = %u\n" , a | b); printf ("a ^ b = %u\n" , a ^ b); printf ("~a = %u\n" , ~a); printf ("a << 1 = %u\n" , a << 1 ); printf ("a >> 1 = %u\n" , a >> 1 ); int num = 7 ; if (num & 1 ) { printf ("%d 是奇数\n" , num); } return 0 ; }
2.差分 差分常用于对一个数组的区间进行频繁操作修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int s[]={-1 ,0 ,5 ,2 ,3 ,4 };int d[6 ];int s1[6 ];d[0 ]=s[0 ]; for (int i=1 ;i<n;++i){ d[i]=s[i]-s[i-1 ]; } d[1 ]+=1 ; d[3 +1 ]-=1 ; s1[0 ]=d[0 ]; for (int i=1 ;i<n;++i){ s1[i]=d[i]+s1[i-1 ]; }
3.质数筛 埃氏筛:因为如果一般双循环找质数(素数)可能查找有重复部分,时间就会大大增长,这时,可以用以下办法:
1 2 3 4 5 6 7 8 9 void sieve (bool is_prime[], int n) { for (int i = 2 ; i * i <= n; i++) { if (is_prime[i]) { for (int j = i * i; j <= n; j += i) { is_prime[j] = false ; } } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int main () { int n = 30 ; bool is_prime[100 ]; for (int i = 0 ; i <= 100 ; i++) is_prime[i] = true ; is_prime[0 ] = is_prime[1 ] = false ; sieve(is_prime, n); for (int i = 2 ; i <= n; i++) { if (is_prime[i]) printf ("%d " , i); } return 0 ; }
4.哈希表 hash表用于频繁查找的操作,适合需要快速查找,删除,增加的操作,常用于缓存,便于快速查找,提高性能
但是,注意:与链表不能完全用途重合,链表适用于频繁地删除和修改操作 ,是动态的 ,而hash是固定的 ,有时修改需要改变函数
实现的话说白了,就是开一个数组,数组的索引就是我们需要查找的元素 ,对应索引的值就是我们需要进行操作的数值(次数,标记)
唯一细节的是,要根据开的hash数组的大小z,对每个我们要查找的元素%z来构成索引
下面以一个字符数组 为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <stdio.h> #include <string.h> #define SIZE 10 int hash (char * str) { int hashValue = 0 ; while (*str) { hashValue += *str; str++; } return hashValue % SIZE; int main () { char * keys[] = {"apple" , "banana" , "orange" , "apple" }; int counts[SIZE] = {0 }; for (int i = 0 ; i < 4 ; i++) { int index = hash(keys[i]); counts[index]++; } for (int i = 0 ; i < SIZE; i++) { if (counts[i] > 0 ) { printf ("Index %d: %d times\n" , i, counts[i]); } } return 0 ; }
但是当2个不同的元素余数相同时,就会引发哈希冲突,这是不可避免的,这时,可以用链地址法:就是把这2个不同的元素存入相同的索引哈希中,一个位置满了,就插到下一个位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #define SIZE 5 typedef struct Node { char word[50 ]; int count; struct Node * next ; } Node; Node* hashTable[SIZE]; int hash (char * str) { int hashValue = 0 ; while (*str) { hashValue += *str; str++; } return hashValue % SIZE; } void insert (char * word) { int index = hash(word); Node* temp = hashTable[index]; while (temp != NULL ) { if (strcmp (temp->word, word) == 0 ) { temp->count++; return ; } temp = temp->next; } Node* newNode = (Node*)malloc (sizeof (Node)); strcpy (newNode->word, word); newNode->count = 1 ; newNode->next = hashTable[index]; hashTable[index] = newNode; }
5.链表 链表由多个节点组成,这一个个节点中又有数据域(存数据的) 和指针域(用于指向链表的下一个节点地址,通常最后一个节点指向NULL)
在上一个哈希表中,遇到的哈希冲突问题,就可以用链表解决,当不同元素的余数相同时,我们就可以把他存入这个哈希槽位的链表中
1 2 3 4 5 6 typedef struct Node { char word[50 ]; struct Node * next ; } Node;
插入操作:
1 2 3 4 5 6 7 void insertAtHead (Node** head, char * word) { Node* newNode = (Node*)malloc (sizeof (Node)); strcpy (newNode->word, word); newNode->count = 1 ; newNode->next = *head; *head = newNode; }
如果要删除则是把该节点的next指针 指向当前的next指针
6.文件操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 FILE *fp; fp = fopen("test.txt" , "w" ); if (fp == NULL ) { printf ("无法打开文件\n" ); return 1 ; } fprintf (fp, "Hello, World!\n" );fclose(fp); fp = fopen("test.txt" , "r" ); char buffer[100 ];while (fgets(buffer, 100 , fp) != NULL ) { printf ("%s" , buffer); } fclose(fp);
7.DFS深度搜索 构造基本就是标记再加上递归回溯:
1.遍历数组的所有的数,以n个每行输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int path[6 ];void dfs (int x,int n,int k) { if (x>n){ for (int i=1 ;i<=n;++i){ printf ("%d " ,path[i]); } printf ("\n" ); return ; } for (int i=1 ;i<=k;++i){ path[x]=i; dfs(x+1 ,n,k); } }
2.带有标记性质的dfs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int dir[8 ][2 ]={{1 ,0 },{-1 ,0 },{0 ,1 },{0 ,-1 },{1 ,-1 },{-1 ,1 },{1 ,1 },{-1 ,-1 }};char s[110 ][110 ];int visited[110 ][110 ];int dfs (int x,int y,int n,int m) { visited[x][y]=1 ; int count=1 ; for (int i=0 ;i<8 ;++i){ int nx=x+dir[i][0 ]; int ny=y+dir[i][1 ]; if (s[nx][ny]=='W' && !visited[nx][ny] && nx>=0 && nx<n && ny>=0 && ny<m){ count+=dfs(nx,ny,n,m); } } return count; }
8.二维矩阵压缩为一维–最大加权矩形 矩阵压缩问题常用于找最大子矩阵的和:
因为如果用循环,4个for的时间复杂度过于大
通过矩阵压缩,一个n*n的矩阵,可以把他通过i到j行的数据加起来压缩一下,变成1维问题,再来找最大的和
1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 temp[0 ] = 1 + 4 = 5 temp[1 ] = 2 + 5 = 7 temp[2 ] = 3 + 6 = 9
9.二分–查找 前提:单调 序列
原理:通过不断找中间索引,看看中间值是否等于所要值,如果并不是,那更新左右边界,并且再次中间插
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int binarySearch (int arr[], int n, int target) { int left = 0 , right = n - 1 ; while (left <= right) { int mid = left + (right - left) / 2 ; if (arr[mid] == target) { return mid; } else if (arr[mid] < target) { left = mid + 1 ; } else right = mid - 1 ; } return -1 ; }
10.队列:先进先出 (想象成一个U型通道dui)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <stdio.h> #define MAX_SIZE 100 int queue [MAX_SIZE];int front = 0 , rear = 0 ;void enqueue (int x) { if (rear == MAX_SIZE) { printf ("队列已满\n" ); return ; } queue [rear++] = x; } int dequeue () { if (front == rear) { printf ("队列为空\n" ); return -1 ; } return queue [front++]; } int main () { enqueue(10 ); enqueue(20 ); printf ("出队元素: %d\n" , dequeue()); printf ("出队元素: %d\n" , dequeue()); return 0 ; }
11.栈:后进先出 (想象成一个I型通道,堵了)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <stdio.h> #define MAX_SIZE 100 int stack [MAX_SIZE];int top = -1 ;void push (int x) { if (top == MAX_SIZE - 1 ) { printf ("栈已满\n" ); return ; } stack [++top] = x; } int pop () { if (top == -1 ) { printf ("栈为空\n" ); return -1 ; } return stack [top--]; } int main () { push(10 ); push(20 ); printf ("出栈元素: %d\n" , pop()); return 0 ; }
12.保留最后n个数字: 1.字符串*10^位数
2.数字%10^4
同余与二维前缀和问题移步北航刷题整集