IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 算法基础课 acwing -> 正文阅读

[数据结构与算法]算法基础课 acwing

算法基础课

本博客基于acwing算法基础课,所做笔记
目的在于方便复习
课程链接:https://www.acwing.com/activity/content/introduction/11/
主讲人:yxc

上课的时候理解算法的主要思想

课后把模板背过,并且调试题目,多重复,多敲

第一讲 基础算法

快速排序

image-20210823141603268

调整区间的方法:

1、暴力,需要开辟两个数组

image-20210823141732700

2、双指针

image-20210823141957365

1、i往后面扫描,当i指向的数字小于等于x,就继续往后,当指向的数字大于x的时候停下

2、此时j往前面扫描,当j指向的数字大于x,就继续往前,当指向的数字小于x的时候,停下

3、此时i指向的大于x,j指向的小于x,那么这两个数字交换,并且两个指针都往中间移动一位,直到i和j穿过

可以发现,任何时候,i右边的数字都小于等于x,j左边的数字都大于x

模板:

void quick_sort(int q[],int l,int r){
	if(l >= r) return;				// 如果区间内只有一个或者没有数字,跳出循环
	
	int x = q[l + r >> 1],i = l -1,j = r + 1;
	
	while(i < j){
		do i++; while(q[i] < x);
		do j--; while(q[j] > x);
		if(i < j) swap(q[i],q[j]);
	}
	
	quick_sort(q,l,j);
	quick_sort(q,j+1,r);
}

快速排序

https://www.acwing.com/problem/content/787/

#include <bits/stdc++.h>

using namespace std;

const int N = 1e6 + 10;

int q[N];

void quick_sort(int q[],int l,int r){
	if(l >= r) return;	
	
	int x = q[l + r >> 1],i = l -1,j = r + 1;
	
	while(i < j){
		do i++; while(q[i] < x);
		do j--; while(q[j] > x);
		if(i < j) swap(q[i],q[j]);
	}
	
	quick_sort(q,l,j);
	quick_sort(q,j+1,r);
}

int main(){
	int n;
	scanf("%d",&n);
	for(int i = 0;i < n;i++) scanf("%d",&q[i]);
	
	quick_sort(q,0,n-1);
	
	for(int i = 0;i < n;i++) printf("%d ",q[i]);
	
	return 0;
}

第k个数

https://www.acwing.com/problem/content/788/

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

int a[N],n,k;

int quick_sort(int l,int r,int k){
	if(l == r) return a[l];
	
	int i = l - 1,j = r + 1,x = a[(l+r) >> 1];
	while(i < j){
		while(a[++ i] < x);		// 这里只能是++i,不能是i-- 
		while(a[-- j] > x);
		if(i < j)  swap(a[i],a[j]);
	}
	int sl = j - l + 1;
	if(k <= sl) return quick_sort(l,j,k);
	else return quick_sort(j+1,r,k-sl);
}

int main(){
	scanf("%d%d",&n,&k);
	for(int i = 0 ;i < n;i++) scanf("%d",&a[i]);
	printf("%d\n",quick_sort(0,n-1,k));
	return 0;
}

快速选择算法

image-20210829144929837

归并排序

image-20210823145441744

如何归并

双指针

image-20210823145721003

image-20210823150047410

归并的思路:

1、先递归,将序列一直分半,直到只剩一个元素

2、归并,归并采取双指针

模板:

void merge_sort(int q[],int l,int r){
	if(l >= r) return;				// 如果区间只有一个或者没有数字,不用递归了 
	
	int mid = (l + r) >> 1;			// mid是中间那个数字 
	
	merge_sort(q,l,mid);			// 归并排序先递归 
	merge_sort(q,mid+1,r);			// 分到最后就只剩一个数字了 
	
	int k = 0,i = l,j = mid + 1;	// 归并,使用双指针,i指向左半边的起点,j指向右半边的起点 
	while(i <= mid && j <= r){		
		if(q[i] <= q[j]) tmp[k++] = q[i++];
		else tmp[k++] = q[j++];
	}
	while(i <= mid) tmp[k++] = q[i++];
	while(j <= r) tmp[k++] = q[j++];
	
	for(i = l,j = 0;i <= r;i++,j++) q[i] = tmp[j];
}

归并排序

https://www.acwing.com/problem/content/789/

#include <bits/stdc++.h>

using namespace std;

const int N = 1e6 + 10;

int q[N],tmp[N],n;

void merge_sort(int q[],int l,int r){
	if(l >= r) return;				// 如果区间只有一个或者没有数字,不用递归了 
	
	int mid = (l + r) >> 1;			// mid是中间那个数字 
	
	merge_sort(q,l,mid);			// 归并排序先递归 
	merge_sort(q,mid+1,r);			// 分到最后就只剩一个数字了 
	
	int k = 0,i = l,j = mid + 1;	// 归并,使用双指针,i指向左半边的起点,j指向右半边的起点 
	while(i <= mid && j <= r){		
		if(q[i] <= q[j]) tmp[k++] = q[i++];
		else tmp[k++] = q[j++];
	}
	while(i <= mid) tmp[k++] = q[i++];
	while(j <= r) tmp[k++] = q[j++];
	
	for(i = l,j = 0;i <= r;i++,j++) q[i] = tmp[j];
}

int main(){
	scanf("%d",&n);
	for(int i = 0;i < n;i++) scanf("%d",&q[i]);
	
	merge_sort(q,0,n-1);
	
	for(int i = 0;i < n;i++) printf("%d ",q[i]);
	
	return 0;
}

逆序对的数量

https://www.acwing.com/problem/content/790/

#include <bits/stdc++.h> 

using namespace std;

const int N = 1e5 + 10;

int a[N],temp[N];

long long merge_sort(int l,int r){
	if(l >= r) return 0;
	
	int mid = (l + r) >> 1;
	long long res = merge_sort(l,mid) + merge_sort(mid+1,r);
	
	// 归并
	int k = 0,i = l,j = mid + 1;
	while(i <= mid && j <= r) {
		if(a[i] <= a[j]) temp[k++] = a[i++];
		else {
			temp[k++] = a[j++];
			res += mid - i + 1;
		}
	}
	
	while(i <= mid) temp[k++] = a[i++];
	while(j <= r) temp[k++] = a[j++];
	
	for(i = l,k = 0;i <= r;i++,k++) a[i] = temp[k];
	return res;
}

int main(){
	int n;
	scanf("%d",&n);
	for(int i = 0;i < n;i++) scanf("%d",&a[i]);
	printf("%lld\n",merge_sort(0,n-1));
	return 0;
}

image-20210829151700702

二分

模板:https://www.acwing.com/blog/content/31/

bool check(int x) {/* ... */} // 检查x是否满足某种性质

// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;    // check()判断mid是否满足性质
        else l = mid + 1;
    }
    return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

image-20210823154253988

数的范围

https://www.acwing.com/problem/content/791/

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

int q[N],m,n;

int main(){
	scanf("%d %d",&n,&m);
	for(int i = 0;i < n;i++) scanf("%d",&q[i]);
	
	while(m--){
		int x;
		scanf("%d",&x);
		// 先不管check函数,先把l,r和while,mid写好 
		int l = 0,r = n - 1;
		while(l < r){
			int mid = (l + r) >> 1;
			if(q[mid] >= x) r = mid;
			else l = mid + 1;
		}
		if(q[l] != x) puts("-1 -1");
		else{
			printf("%d ",l);
			l = 0,r = n - 1;
			while(l < r){
				int mid = (l + r + 1) >> 1;
				if(q[mid] <= x) l = mid;
				else r = mid - 1;
			}
			printf("%d\n",l);
		}
	}
	return 0;
}

浮点数二分

image-20210823160402292

开平方

#include <bits/stdc++.h>

using namespace std;

int main(){
	double x;
	cin >> x;
	
	double l = 0,r = x;
	while(r - l > 1e-6){
		double mid = (l + r) / 2;
		if(mid * mid >= x) r = mid;
		else l = mid;
	}
	printf("%lf\n",l);
	return 0;
}

数的三次方根

https://www.acwing.com/problem/content/792/

#include <bits/stdc++.h>

using namespace std;

int main(){
	double n;
	scanf("%lf",&n);
	double l = -10000,r = 10000;
	while(r-l > 1e-8){
		double mid = (l + r) / 2;
		if(mid * mid * mid > n) r = mid;
		else l = mid;
	}
	printf("%lf\n",l);
	return 0;
}

高精度

前缀和

一维前缀和

image-20210824140018283

前缀和

https://www.acwing.com/problem/content/797/

预处理:O(n)

#include <bits/stdc++.h> 

using namespace std;

const int N = 1e5 + 10;

int a[N],s[N];

int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= n;i++) {
		scanf("%d",&a[i]);
		s[i] = s[i-1] + a[i];	// 前缀和数组的初始化
	}
	while(m--){
		int l,r;
		scanf("%d%d",&l,&r);
		printf("%d\n",s[r]-s[l-1]);
	}
	return 0;
}

二维前缀和

S[i,j]的含义:这个格子左上角面积之和

image-20210824142157204

S[i,j]计算方法:

image-20210824142941774

(x1,y1),(x2,y2)子矩阵中所有数之和怎么计算?

image-20210824143910149

子矩阵的和

https://www.acwing.com/problem/content/798/

#include <bits/stdc++.h>

using namespace std;

const int N = 1e3 + 10;

int a[N][N],s[N][N];

int main(){
	int n,m,q;
	scanf("%d%d%d",&n,&m,&q);
	for(int i = 1;i <= n;i++){
		for(int j = 1;j <= m;j++){
			scanf("%d",&a[i][j]);
			// 初始化二维前缀和 	
			s[i][j] = a[i][j] + s[i-1][j] + s[i][j-1] - s[i-1][j-1];	
		}
	}
	while(q--){
		int x1,y1,x2,y2;
		scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
		// 计算差 
		printf("%d\n",s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1]);
	}
	return 0;
}

差分

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z6zhrdDJ-1630933432550)(https://gitee.com/Crescent_P/picture-bed/raw/master/hh.png)]差分数组不需要构造

一维差分

https://www.acwing.com/problem/content/799/

#include <bits/stdc++.h> 

using namespace std;

const int N = 1e5 + 10;

int a[N],b[N];

// 插入函数,使得b[l] + c,b[r+1] - c 
void insert(int l,int r,int c){
	b[l] += c;
	b[r + 1] -= c;
}

int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= n;i++){
		scanf("%d",&a[i]);
		// 差分数组不需要构建,先假设a数组为全零,那么b数组也就为全零
        // [1,1]插入a[1],[2,2]插入a[2]
		// 就能构造出差分数组 
		insert(i,i,a[i]);
	}
	while(m--){
		int l,r,c;
		scanf("%d%d%d",&l,&r,&c);
		insert(l,r,c);
	}
	for(int i = 1;i <= n;i++) {
		a[i] = a[i-1] + b[i];
		printf("%d ",a[i]);
	}
}

二维差分

image-20210824153105843

image-20210824153839528

差分矩阵

https://www.acwing.com/problem/content/800/

#include <bits/stdc++.h> 

using namespace std;

const int N = 1e3 + 10;

int a[N][N],b[N][N];

// 插入公式
void insert(int x1,int y1,int x2,int y2,int c){
	b[x1][y1] += c;
	b[x1][y2+1] -= c;
	b[x2+1][y1] -= c;
	b[x2+1][y2+1] += c;
}

int main(){
	int n,m,q;
	scanf("%d%d%d",&n,&m,&q);
	for(int i = 1;i <= n;i++){
		for(int j = 1;j <= m;j++){
			scanf("%d",&a[i][j]);
            // 构建差分数组b
			insert(i,j,i,j,a[i][j]);
		}
	}
	while(q--){
		int x1,y1,x2,y2,c;
		scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&c);
		insert(x1,y1,x2,y2,c);
	}
	for(int i = 1;i <= n;i++){
		for(int j = 1;j <= m;j++){
			a[i][j] = b[i][j] + a[i-1][j] + a[i][j-1]  - a[i-1][j-1];
			printf("%d ",a[i][j]);
		}
		printf("\n");
	}
	return 0;
}

双指针算法

image-20210829160939604

输出单词

#include <bits/stdc++.h> 

using namespace std;

int main(){
	char str[1000];
	
	gets(str);
	
	int n = strlen(str);
	
	for(int i = 0;i < n;i++){
		int j = i;
		while(j < n && str[j] != ' ') j++;
		
		// 这道题的具体逻辑 
		for(int k = i;k < j;k++) printf("%c",str[k]);
		puts("");
		i = j;
	}
	return 0;
}

最长连续不重复子序列

https://www.acwing.com/problem/content/801/

暴力:O(n^2)

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

int a[N],b[N];

int main(){
	int n;
	scanf("%d",&n);
	for(int i = 0;i < n;i++) scanf("%d",&a[i]);
	int ans = 0;
	for(int i = 0;i < n;i++){
		b[a[i]]++;
		for(int j = i+1;j < n;j++){
			if(b[a[j]] != 0) break;
			b[a[j]]++;
			ans = max(ans,j - i + 1);
		}
		b[a[i]]--;
		for(int i = 0;i < n;i++) b[i] = 0;
	}
	printf("%d\n",ans);
	return 0;
}

双指针:O(n)

#include <bits/stdc++.h> 

using namespace std;

const int N = 1e5 + 10;

int a[N],b[N];

int main(){
	int n;
	scanf("%d",&n);
	for(int i = 0;i < n;i++) scanf("%d",&a[i]);
	int ans = 0;
	for(int i = 0,j = 0;i < n;i++){
		while(j < n && b[a[j]] == 0){
			ans = max(ans,j - i + 1);
			b[a[j++]]++;
		}
		b[a[i]]--;
	}
	printf("%d\n",ans);
	return 0;
}

数组元素的目标和

https://www.acwing.com/problem/content/802/

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

int a[N],b[N],n,m,x;

int main(){
	scanf("%d%d%d",&n,&m,&x);
	for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
	for(int j = 1;j <= m;j++) scanf("%d",&b[j]);
	for(int i = 1,j = m;i <= n ;i++){
		while(j <= m && a[i] + b[j] > x) j--;
		if(a[i] + b[j] == x) {
			printf("%d %d",i-1,j-1);
			break;
		}
	}
	return 0;
}

判断子序列

https://www.acwing.com/problem/content/description/2818/

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

int n,m;
int a[N],b[N];

int main(){
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
	for(int j = 1;j <= m;j++) scanf("%d",&b[j]);
	
	bool flag = true;
	for(int i = 1,j = 1;i <= m && flag;i++){
		while(j <= n && b[i] == a[j]) j++,i++;
		// 移动到最后一位后面了 
		if(j == n+1) flag = false;
	}
	if(flag) puts("No");
	else puts("Yes");
	
	return 0;
}

位运算

haha

可以求出10的二进制表示

#include <bits/stdc++.h> 

using namespace std;

int main(){
	int n = 10;
	
	for(int k = 3;k >= 0;k-- ) printf("%d",(n>>k)&1);
	
	return 0;
}

lizi

lowbit运算

image-20210830153204218

image-20210830153435293

二进制中1的个数

https://www.acwing.com/problem/content/803/

#include <bits/stdc++.h> 

using namespace std;

int lowbit(int x){
	return x & -x;
}

int main(){
	int n;
	scanf("%d",&n);
	while(n--){
		int x;
		scanf("%d",&x);
		
		int res = 0;
		// 每次减去x的最后一位1 
		while(x) x -= lowbit(x), res++;
		printf("%d ",res);
	}
	return 0;
}

离散化

lisanhjua

image-20210830154951470

区间和

https://www.acwing.com/problem/content/804/

#include <bits/stdc++.h>

using namespace std;

typedef pair<int,int> PII;

const int N = 3e5 + 10;


int n,m;
int a[N],s[N];

vector<int> alls;
vector<PII> add,query;

// 通过x的值找到下标,将一个值序列,映射到从1开始的数组 
int find(int x){
	int l = 0,r = alls.size() - 1;
	while(l < r){
		int mid = (l + r) >> 1;
		if(alls[mid] >= x) r = mid;
		else l = mid + 1;
	}
	return r + 1;
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i = 0;i < n;i++){
		int x,c;
		scanf("%d%d",&x,&c);
		add.push_back({x,c});
		
		alls.push_back(x);
	}
	
	for(int i = 0;i < m;i++){
		int l,r;
		scanf("%d%d",&l,&r);
		query.push_back({l,r});
		
		alls.push_back(l);
		alls.push_back(r);
	}
	
	// 去重 
	sort(alls.begin(),alls.end());
	alls.erase(unique(alls.begin(),alls.end()),alls.end());
	// 上面操作完,是一个去重并且排序后的一个数组 
	// 处理插入 
	for(auto item: add)	{
		int x = find(item.first);
		// 上面x是通过值找到的下标,x是下标 
		a[x] += item.second;
	}
	
	// 预处理前缀和
	for(int i= 1;i <= alls.size();i++) s[i] = s[i-1] + a[i];
	
	// 处理询问 
	for(auto item : query) {
		int l = find(item.first);
		int r = find(item.second);
		printf("%d\n",s[r] - s[l-1]);
	}
	return 0;
}

区间合并

merge

区间合并

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2x7KVK5l-1630933432558)(算法基础课.assets/image-20210830163830687.png)]

https://www.acwing.com/problem/content/805/

#include<bits/stdc++.h> 

using namespace std;

typedef pair<int,int> PII;

const int N = 1e5 + 10;

int n;
vector<PII> segs;

void merge(vector<PII> &segs){
	vector<PII> res;
	
	// 以左端点排序 
	sort(segs.begin(),segs.end());
	
	int st = -2e9,ed = -2e9;
	
	for(auto seg : segs){
		// 没有交集 
		if(ed < seg.first){
			if(st != -2e9) res.push_back({st,ed});
			st = seg.first,ed = seg.second;
		}else ed = max(ed,seg.second);
	}
	
	// 最后一个 
	if(st != -2e9) res.push_back({st,ed});
	segs = res;
}

int main(){
	scanf("%d",&n);
	
	for(int i = 0;i < n;i++){
		int l,r;
		scanf("%d%d",&l,&r);
		segs.push_back({l,r});
	}
	
	merge(segs);
	
	printf("%d\n",segs.size());
	
	return 0;
}

第二讲 数据结构

单链表

image-20210831153657064

用数组模拟链表

e数组存值,ne数组存next

image-20210831153802072

单链表

https://www.acwing.com/problem/content/828/

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

// head 表示头结点的下标
// e[i] 表示节点i的值
// ne[i] 表示节点i的next指针是多少
// 存储到当前用到了哪个点 
int head,inx,e[N],ne[N];

// 初始化 
void init(){
	// 初始化,链表为空,-1表示空集 
	head = -1;
	inx = 0; 
}

// 将x插到头结点,就是让x成为头结点 
void add_to_head(int x) {
	e[inx] = x;
	ne[inx] = head;
	// head 表示头结点的下标,head不是一个节点哦 
	head = inx;
	inx++;
}

// 将x插入到下标是k的节点的后面 
void add(int k,int x){
	e[inx] = x;
	ne[inx] = ne[k];
	ne[k] = inx;
	inx++;
}

// 将下标是k的点的后面的点删除 
void remove(int k){
	ne[k] = ne[ne[k]];
}

int main(){
    int M;
    scanf("%d",&M);
    init();
    while(M--){
        int k,x;
        char op;
        cin >> op;
        if(op == 'H'){
            scanf("%d",&x);
            add_to_head(x);
        }else if(op == 'D'){
            scanf("%d",&k);
            // 删除头结点,就是让head指向头结点的下一个
            if (!k) head = ne[head];
            else remove(k-1);
        }else{
            scanf("%d%d",&k,&x);
            add(k-1,x);
        }
    }
    for(int i = head;i != -1;i = ne[i]) printf("%d ",e[i]);
    return 0;
}

双链表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jF276k9j-1630933432561)(算法基础课.assets/image-20210831153743826.png)]

image-20210831162815817

双链表

https://www.acwing.com/problem/content/829/

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

int e[N],l[N],r[N],idx;

// 初始化 
void init(){
	// 0 表示右端点
	// 1 表示左端点
	r[0] = 1,l[1] = 0;
	idx = 2;
}

// 在下标是k的点的右边,插入x 
void add(int k,int x){
	e[idx] = x;
	l[idx] = k;
	r[idx] = r[k];
	r[k] = idx;
	l[r[idx]] = idx;
	idx++;
}

// 删除第k个点 
void remove(int k) {
	r[l[k]] = r[k];
	l[r[k]] = l[k];
}


int main(){
	int M;
	scanf("%d",&M);
	init();
	while(M--){
		int x,k;
		string op;
		cin >> op;
		if(op == "L"){
			scanf("%d",&x);
			add(0,x);
		}else if(op == "R"){
			scanf("%d",&x);
			add(l[1],x);
		}else if(op == "D"){
			scanf("%d",&k);
			// idx从2开始,第k个点的下标应该是k+1
			remove(k+1);
		}else if(op == "IL"){
			scanf("%d%d",&k,&x);
			add(l[k+1],x);
		}else{
			scanf("%d%d",&k,&x);
			add(k+1,x);
		}
	}
	
	for(int i = r[0];i != 1;i = r[i]) printf("%d ",e[i]);
	return 0;
}

先进后出

模拟栈

https://www.acwing.com/problem/content/830/

#include <bits/stdc++.h> 

using namespace std;

const int N = 1e5 + 10;

// tt 是栈顶坐标 
int stk[N],tt;

void push(int x) {
	stk[++tt] = x;
}

void pop(){
	tt--;
}

bool empty(){
	if(tt == 0) return true;
	else return false;
}

int query(){
	return stk[tt];
}

int main(){
	int M;
	scanf("%d",&M);
	while(M--){
		string op;
		int x;
		cin >>op;
		if(op == "push"){
			scanf("%d",&x);
			push(x);
		}else if(op == "pop"){
			pop();
		}else if(op == "query"){
			printf("%d\n",query());
		}else{
			if(empty()) printf("YES\n");
			else printf("NO\n");
		}
	}
	return 0;
}

队列

先进先出

模拟队列

https://www.acwing.com/problem/content/831/

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5+10;

// 在队尾插入元素,在队头弹出原始 
int q[N],hh,tt = -1;

void push(int x) {
	q[++ tt] = x;
}

void pop(){
	hh++;
}

bool empty(){
	if(hh <= tt) return false;
	else return true;
}

int query(){
	return q[hh];
}

int main(){
	int M;
	scanf("%d",&M);
	while(M--){
		string op;
		int x;
		cin >> op;
		if(op == "push"){
			scanf("%d",&x);
			push(x);
		}else if(op == "pop"){
			pop();
		}else if(op == "query"){
			printf("%d\n",query());
		}else{
			if(empty()) printf("YES\n");
			else printf("NO\n");
		}
	}
	return 0;
}

单调栈

image-20210831184549374

单调栈

常见模型:找出每个数左边离它最近的比它大/小的数

https://www.acwing.com/problem/content/description/832/

O(n)

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

int n;
// tt一开始指向0,指向0表示没有元素 
int stk[N],tt;

int main(){
	scanf("%d",&n);
	for(int i = 0;i < n;i++){
		int x;
		scanf("%d",&x);
		// 如果栈不空,并且栈顶元素大于x,弹栈 
		while(tt && stk[tt] >= x) tt--;
		// 如果栈不空,输出栈顶 
		if(tt) printf("%d ",stk[tt]);
		else printf("-1 ");
		// 压栈,因为对于后面的元素而言,这个是离它最近,并且小的数了 
		stk[++tt] = x;
	}
	return 0; 
}

单调队列

常见模型:找出滑动窗口中的最大值/最小值

base on 董晓 : https://www.bilibili.com/video/BV1H5411j7o6

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R3TivCQf-1630933432561)(https://gitee.com/Crescent_P/picture-bed/raw/master/image-20210901142939496.png)]

image-20210901143254248

image-20210901143506485

滑动窗口

https://www.acwing.com/problem/content/156/

#include <bits/stdc++.h> 

using namespace std;

const int N = 1e6 + 10;

int a[N],q[N],hh,tt = -1;

int main(){
	int n,k;
	scanf("%d%d",&n,&k);
	for(int i = 0;i < n;i++) scanf("%d",&a[i]);
	for(int i = 0;i < n;i++){
		// q[hh] 不在[i-k+1,i],队头出队,就是判断队头是否已经滑出了窗口
		// 队列中存的是下标 
		if(hh <= tt && q[hh] < i-k+1) hh++;
		// 如果要进来的元素比队列中的元素小,那么队尾元素出队,保证队列的元素是由小到大
		// 因为较小的还用的上,大了的就用不上了 
		while(hh <= tt && a[q[tt]] >= a[i]) tt--;
		// 当前元素的下标入队 
		q[++tt]  = i; 
		// 保证窗口内的元素足够才开始输出
		// 队首存的是最小元素的下标 
		if(i >= k - 1) printf("%d ",a[q[hh]]);
	}
	puts("") ;
	hh = 0,tt = -1;
	for(int i = 0;i < n;i++){
		if(hh <= tt && i-k+1>q[hh]) hh++;
		while(hh <= tt && a[q[tt]] <= a[i]) tt--;
		q[++tt] = i;
		if(i >= k-1 ) printf("%d ",a[q[hh]]);
	}
	return 0;
}

KMP

image-20210901152508092

image-20210901153639974

image-20210901154845219

KMP字符串

https://www.acwing.com/problem/content/833/

#include <bits/stdc++.h>

using namespace std;

const int N = 1e4+10,M =  1e5+10;

int n,m;
char p[N],s[M];
int ne[N];

int main(){
	// 从第1位开始 
	cin >> n >> p+1 >> m >> s+1;
	
	// 求next数组 
	// next[1]=0,因为是j+1来匹配 
	for(int i = 2,j = 0;i <= n;i++) {
		// 如果匹配不成功 
		while(j && p[i]!=p[j+1]) j = ne[j];
		// 匹配成功 
		if(p[i] == p[j+1]) j++;
		ne[i] = j;
	}
	
	// kmp匹配过程
	for(int i = 1,j = 0;i <= m;i++) {
		while(j && s[i] != p[j+1]) j = ne[j];
		if(s[i] == p[j+1]) j++;
		// 匹配完了 
		if(j == n){
			// 下标从1开始的所以要减去1 ,应该是 i-n+1-1 
			printf("%d ",i-n);
			j = ne[j];	//
		}
	}
	return 0;
}

Trie

高效地存储和查找字符串集合的数据结构

要么全是大写,要么全是小写,要么全是数字,类型不会特别多

image-20210901161557289

Trie字符串统计

https://www.acwing.com/problem/content/837/

#include <bits/stdc++.h> 

using namespace std;

const int N = 1e5 + 10;

// 下标是0的点,既是根节点,又是空节点 
int son[N][26], cnt[N],idx;
char str[N];

void insert(char str[]){
	// 从根节点开始 
	int p = 0;
	// c++ 字符串以0结尾,就可以用这个来当条件 
	for(int i = 0;str[i];i++){
		// 将小写字母映射成数字 
		int u = str[i] - 'a';
		// 如果不存在这个子节点 
		if(!son[p][u]) son[p][u] = ++idx;
		p = son[p][u];
	}
	// 以这个点结尾的单词个数++ 
	cnt[p] ++;
}

int query(char str[]){
	int p = 0;
	for(int i = 0;str[i];i++){
		int u = str[i] - 'a';
		if(!son[p][u]) return 0;
		p = son[p][u];
	}
	return cnt[p];
}


int main(){
	int n;
	scanf("%d",&n);
	while(n--){
		char op[2];
		scanf("%s%s",&op,&str);
		if(op[0] == 'I') insert(str);
		else printf("%d\n",query(str));
	}
}

并查集

image-20210901165836918

合并集合

https://www.acwing.com/problem/content/838/

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

int n,m;
int p[N];

// 返回x祖宗节点 + 路径压缩 
int find(int x){
	// 如果不是根节点,就让他的父节点等于祖宗节点 
	if(p[x] != x) p[x] = find(p[x]);
	return p[x];
}

int main(){
	scanf("%d%d",&n,&m);
	
	for(int i = 1;i <= n;i++) p[i] = i;
	
	while(m--){
		char op[2];
		int a,b;
		scanf("%s%d%d",op,&a,&b);
		if(op[0] == 'M'){
			// a的祖宗节点的父节点 = b的祖宗节点 
			p[find(a)] = find(b);
		}else{
			if(find(a) == find(b)) puts("Yes");
			else puts("No");
		}
	}
	return 0;
} 

连通块中点的数量

https://www.acwing.com/problem/content/839/

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

int n,m;
int p[N],size[N];

int find(int x){
	if(p[x] != x) p[x] = find(p[x]);
	return p[x];
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= n;i++){
		p[i] = i;
		size[i] = 1;
	}
	while(m--){
		int a,b;
		char op[2];
		scanf("%s",op);
		if(op[0] == 'C'){
			scanf("%d%d",&a,&b);
			if(find(a) != find(b)) size[p[b]] += size[p[a]];
			p[find(a)] = find(b);
		}else if(op[1] == '1'){
			scanf("%d%d",&a,&b);
			if(find(a) == find(b)) puts("Yes");
			else puts("No");
		}else{
			scanf("%d",&a);
			printf("%d\n",size[find(a)]);
		}
	}
	return 0;
}

heap

image-20210902115348563

image-20210902120100393

哈希表

image-20210903191406675

STL

/*
STL
vector						变长数组,倍增 
	size()	返回元素个数
	empty()	返回是否为空
	clear()	清空
	front()/back()
	push_back/pop_back()
	begin()/end()
	[]
	
pair<int,int> 
	first 	第一个元素 
	second	第二个元素
	支持比较运算,按字典序,以first为第一关键字 
 
string						字符串(),substr,c_str() 
	size()/length()
	empty()
	clear()

queue		队列			
	push()		向队尾插入一个元素 
	front()		返回队头元素
	back()		返回队尾元素 
	pop() 		弹出队头元素
	 
priortity_queue 	优先队列,默认是大根堆 
	push()		插入一个元素 
	top()		返回堆顶元素 
	pop() 		弹出队头元素 

stack				栈	
	push() 		向栈顶插入元素 
	top()		返回栈顶元素 
	pop() 		弹出栈顶的元素 
	size()
	empty() 
deque			双端队列	 
	size()
	empty() 
	clear()
	front()
	back()
	push_back()/pop_back()
	push_front()/pop_front()
	begin()/end()
	[]
	 
set,map,multiset,multimap	基于平衡二叉树(红黑树),动态维护有序序列 
	size()
	empty()
	clear()
	begin()/end()	++,--	返回前驱和后继 
	
	set/multiset
		insert()	插入一个数 
		find()		查找一个数
		count()		返回一个数的个数
		erase()
			(1)	输入是一个数X,删除所有的X	O(k+logn) 
			(2) 输入是一个迭代器,删除这个迭代器 
		lower_bound/upper_bound()
			lower_bound(x)	 返回大于等于x的最小的数的迭代器 
			upper_bound(x) 	 返回大于x的最小的数的迭代器
			
	map/multimap
		insert()	插入的是一个pair 
		erase() 	输入的是pair或者迭代器
		find()
		[] 			O(logn)
		lower_bound/upper_bound()

// 无序的 
unordered_set,unordered_map,unordered_multiset,unordered_multimap 	哈希表 
	和上面类似,增删改查的时间复杂度是O(1) 
	不支持 lower_bound/upper_bound(),迭代器的++,-- 


bitset		压位 
	
	
*/ 

#include <bits/stdc++.h>

using namespace std;

int main() {
	// vector 
	// 定义vector 
	vector<int> va;
	vector<int> vb(10); 
	//长度为10,并且每个元素都是3 
	vector<int> vc(10,3);
	for(int i = 0;i < 10;i++) va.push_back(i);
	// 三种遍历方式 
	for(int i = 0;i < 10;i++) printf("%d ",va[i]);
	puts("");
	for(vector<int>::iterator i = va.begin();i != va.end();i++) printf("%d ",*i);
	puts("");
	for(auto x : va) printf("%d ",x);
	puts("");
	
	// pair
	pair<int,string>  p;
	p = make_pair(1,"cp");
	p = {20,"cpcp"};
	
	// string
	string sa = "123";
	sa += "456";
	cout << sa << endl;
	
	// 子串,第一个参数起始地址,第二个参数长度 
	cout << sa.substr(2,4) << endl;
	// c_str(),字符串起始的长度 
	printf("%s\n",sa.c_str());
	
	// queue 队列 
	queue<int> q;
	
//	q = queue<int>;
	
	// 优先队列 
	priority_queue<int> heap;							// 大根堆 
	
//	priority_queue<int,vector<int,greater<int>> heap;	// 小根堆 
	
	// set
	
	// map
	map<string,int> ma;
	ma["cp"]  = 1;
	printf("%d",ma["cp"]);
	
	return 0;
}

第三讲 搜索与图论

image-20210904101542540

DFS

每个DFS都对应了一个搜索树

image-20210904103614048

排列数字

https://www.acwing.com/problem/content/844/

#include <bits/stdc++.h> 

using namespace std;

const int N = 10;

int n,path[N];
bool st[N];

void dfs(int u){
	if(u == n) {
	    for(int i = 0;i < n;i++) printf("%d ",path[i]);
	    puts("");
	    return;
	}
	for(int i = 1;i <=n;i++){
		if(!st[i]) {
			path[u] = i;
			st[i] = true;
			dfs(u+1);
			// 恢复现场 
			st[i] = false;
			path[u] = 0;
		}
	}
}

int main(){
	scanf("%d",&n);
	dfs(0);
	return 0;
}

BFS

image-20210904142518897

走迷宫

https://www.acwing.com/problem/content/846/

#include <bits/stdc++.h>

using namespace std;

typedef pair<int,int> PII;

const int N =110;

int n,m;
int g[N][N];
int d[N][N];
queue<PII> q;


int bfs(){
	q.push({0,0});
	memset(d,-1,sizeof d);
	d[0][0] = 0;
	int dx[4] = {1,0,-1,0},dy[4] = {0,1,0,-1};
	while(!q.empty()){
		auto t = q.front();
		q.pop();
		for(int i = 0;i < 4;i++){
			int x = t.first + dx[i];
			int y = t.second + dy[i];
			if(x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && d[x][y] == -1){
				q.push({x,y});
				d[x][y] = d[t.first][t.second] + 1;
			}
		}
	}
	return d[n-1][m-1];
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i = 0;i < n;i++){
		for(int j = 0;j < m;j++) scanf("%d",&g[i][j]);
	}
	printf("%d\n",bfs());
	return 0;
}

树与图的深度优先遍历

在算法题中,不管是有向图还是无向图,我们都视为有向图

如果是无向图,那么我们就设置一条a->b,再设置一条b->a的边就行了

image-20210904150009527

图的深搜

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10,M = N * 2;

int n,m;
int h[N],e[M],ne[M],idx;
bool st[N];

// 在a的链接表中,插入一个b 
void add(int a,int b){
	// 头插法 
	n[idx] = b;
	ne[idx] = h[a];
	h[a] = idx++;
}

// 深搜 
void dfs(int u){
	// 标记一下,已经被搜索过了 
	st[u] = true;
	// 遍历连接表 
	for(int i = h[u];i != -1;i = ne[i]){
		int j = e[i];
		if(!st[j]) dfs(j);
	}
}




int main(){
	// 都没有连接,-1表示尾节点 
	memset(h,-1,sizeof h);
}

树的重心

https://www.acwing.com/problem/content/description/848/

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10,M = N * 2;

int n,m;
// h[N]就是头节点,只是因为有多个链表,所以变为数组了 
int h[N],e[M],ne[M],idx;
bool st[N];
 
int ans = N; 

// 在a的链接表中,插入一个b 
void add(int a,int b){
	// 头插法 
	e[idx] = b;
	ne[idx] = h[a];
	h[a] = idx++;
}

// 以u为根的子树中点的数量 
int dfs(int u){
	// 标记一下,已经被搜索过了 
	st[u] = true;
	// sum 当前子树的大小
	// res 每个连通块大小的最大值 
	int sum = 1,res = 0; 
	// 遍历连接表 
	for(int i = h[u];i != -1;i = ne[i]){
		// j 是相邻的点 
		int j = e[i];
		
		if(!st[j]) {
			// 与u相邻的点的子树的节点个数 
			int s = dfs(j);
			// 找出连通块的最大值 
			res = max(res,s);
			// 当前子树的大小,要加上当前点各个子节点为根的子树的大小
			sum += s;
		}
	}
	//  n - sum 是上面连通块的节点个数 
	res = max(res,n - sum);
	// res是连通块的最大值,我们要找到连通块最大的最小值 
	ans = min(res,ans);
	return sum;
}




int main(){
	cin >> n;
	// 都没有连接,-1表示尾节点 
	memset(h,-1,sizeof h);
	for(int i = 0;i < n;i++){
		int a,b;
		cin >> a >> b;
		add(a,b);
		add(b,a);
	}
	dfs(1);
	cout << ans << endl;
	return 0;
}

树与图的广度优先遍历

image-20210904154454144

图中点的层次

https://www.acwing.com/problem/content/849/

#include <bits/stdc++.h> 

using namespace std;

const int N = 1e5 + 10;

int n,m;
int h[N],e[N],ne[N],idx;
int d[N];
queue<int> q;

void add(int a,int b){
	e[idx] = b;
	ne[idx] = h[a];
	h[a] = idx++;
}

int bfs(){
	q.push(1);
	memset(d,-1,sizeof d);
	d[1] = 0;
	while(!q.empty()){
		int t = q.front();
		q.pop();
		for(int i = h[t];i != -1;i=ne[i]){
			int j = e[i];
			if(d[j] == -1){
				d[j] = d[t]+1;
				q.push(j);
			}
		}
	}
	return d[n];
}

int main(){
	cin >> n >> m;
	memset(h,-1,sizeof h);
	for(int i = 0;i < m;i++){
		int a,b;
		scanf("%d%d",&a,&b);
		add(a,b);
	}
	printf("%d\n",bfs());
	return 0;
}

拓扑排序

image-20210904160421879

入度为零都可以作为起点

image-20210904160745496

有向图的拓扑序列

https://www.acwing.com/problem/content/description/850/

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

int n,m;

int h[N],e[N],ne[N],idx;

int q[N],d[N];

void add(int a,int b){
	e[idx] = b;
	ne[idx] = h[a];
	h[a] = idx++;
}

bool topsort(){
    int hh = 0,tt =-1;
	for(int i = 1;i <= n;i++) {
		// 入度为0的点入队,队尾入队
		if(!d[i]) q[++tt] = i;
	}
	while(hh <= tt) {
	    // 队头出队
		int t = q[hh++];
		for(int i = h[t];i != -1;i = ne[i]){
			int j = e[i];
			// 删除i->j,就删除了j的入度
			d[j]--;
			// j 入队
			if(d[j] == 0) q[++tt]=j;
		}
	}
	// 队尾到n-1,表示全部加入,没有环 
	return tt == n -1;
}

int main(){
	cin >> n >> m;
	memset(h,-1,sizeof h);
	for(int i = 0;i < m;i++){
		int a,b;
		cin >> a >> b;
		add(a,b);
		// 入度 
		d[b]++;
	}
	if(topsort()){
		for(int i = 0;i < n;i++) {
			printf("%d ",q[i]);
		}
	}else puts("-1");
	
	return 0;
}

最短路问题

单源:只有一个起点

多源:有多个起点

image-20210906180430125

稠密图:m~n^2 : 邻接矩阵,因为点多

稀疏图:m~n : 邻接表

朴素Dijkstra

image-20210906181928773

Dijkstra求最短路 I

https://www.acwing.com/problem/content/851/

#include <bits/stdc++.h> 

using namespace std;

const int N = 510;

int n,m;
// 稠密图,因为点多,用邻接矩阵 
int g[N][N];
int dist[N];
bool st[N];

int dijkstra(){
    // 初始化距离都为正无穷
	memset(dist,0x3f,sizeof dist);
	
	dist[1] = 0;
	
	// 迭代n次 
	for(int i = 0;i < n;i++){
		int t = -1;
		// 每次找S中距离最小的点 
		 for(int j = 1;j <= n;j++){
		 	//  t == -1 表示是第一个不在S中的点
			//  dist[t] > dist[j] 表示找到最小值 
		 	if(!st[j] && (t == -1 || dist[t] > dist[j])) t = j;
		}
		// t这个点用过了
	 	st[t] = true;
		 	
	 	for(int j = 1;j <= n;j++) {
	 		// g[t][j]	t到j的距离 
	 		// 更新距离
	 		dist[j] = min(dist[j],dist[t] + g[t][j]);
		}
	}
	if(dist[n] == 0x3f3f3f3f) return -1;
	return dist[n];
}

int main(){
	scanf("%d%d",&n,&m);
	
	// 初始化
	memset(g,0x3f,sizeof g);
	
	// 因为边的权值都是正的,所以存在自环也没关系,因为最短路不可能走自环 
	while(m--){
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		// 有重边,选最短的 
		g[a][b] = min(g[a][b],c);
	}
	
	int t = dijkstra();
	
	printf("%d\n",t);
	
	return 0;
}

堆优化Dijkstra

image-20210906191752915

Dijkstra求最短路 II

https://www.acwing.com/problem/content/852/

#include <bits/stdc++.h>

using namespace std;

const int N = 2e5 + 10;

// 距离,编号 
typedef pair<int,int> PII;

// 稀疏图,用邻接表,重边也无所谓,因为代码能保证选出最小的边 
int h[N],e[N],ne[N],w[N],idx;
int n,m;
int dist[N];
bool st[N];

void add(int a,int b,int c){ 
	e[idx] = b;
	ne[idx] = h[a];
	// 权重 
	w[idx] = c;
	h[a] = idx++;
}

int dijkstra(){
	memset(dist,0x3f,sizeof dist);
	dist[1] = 0;
	
	// 小根堆 
	priority_queue<PII,vector<PII>,greater<PII>> heap;
	heap.push({0,1});
	
	while(heap.size()){
		// 距离最小点 
		auto t = heap.top();
		heap.pop();
		
		// 编号 
		int ver = t.second;
		int distance = t.first;
		// 这个点之前出现过了 
		if(st[ver]) continue; 
		
		// 遍历ver的相邻的点 
		for(int i = h[ver];i != -1;i = ne[i]) {
			int j = e[i];
			// w[i]是j和ver之间的距离,插入就是这样插入的 
			// 如果通过ver的距离更小,就更新一下,虽然堆中会存在冗余 
			if(dist[j] > dist[ver] + w[i]) {
			    dist[j] = dist[ver] + w[i];
			    heap.push({dist[j],j});
			}
		}
		st[ver] = true;
	}
	if(dist[n] == 0x3f3f3f3f) return -1;
	else return dist[n];
}

int main(){
	scanf("%d%d",&n,&m);
	
	memset(h,-1,sizeof h);
	
	while(m--){
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		add(a,b,c);
	}
	
	int t = dijkstra();
	
	printf("%d\n",t);
	
	return 0;
}

Bellman-Ford 算法

image-20210906193035991

有边数限制的最短路

https://www.acwing.com/problem/content/description/855/

#include <bits/stdc++.h>

using namespace std;

const int N = 500 + 10,M = 10010;

int n,m;
int dist[N],backup[N];

struct Edge{
	int a,b,w;
}edges[N];

int bellman_ford(){
	memset(dist,0x3f;sizeof dist);
	// 不超过k条边,那么就只迭代k次 
	for(int i = 0;i < k;i++){
		// 可能会发生串联,那么需要备份
		// 这次迭代的结果影响了这次后面的迭代
		// 每次迭代的都使用上次跌带后的结果 
		memcpy(backup,dist,sizeof dist);
		// 遍历所有的边 
		for(int j = 0 ;j < m;++){
			int a = edges[j].a;
			int b = edges[j].b;
			int w = edges[j].w;
			// backup是上次迭代完之后的结果 
			dist[b] = min(dist[b],backup[a] + w);
		}
	}
	// 可能存在负权边 
	if(dist[n] > 0x3f3f3f3f/2) return -1;
	else return  dist[n];
}

int main(){
	scanf("%d%d%d",&a,&b,&c);
	
	for(int i = 0;i < m;i++){
		int a,b,w;
		scanf("%d%d%d",&a,&b,&w);
		// 存边 
		edges[i] = {a,b,w};
	}
	
	int t = bellman_ford();
	
	if(t == -1) puts("impossible");
	else printf("%d\n",t);
	
	return 0;
}

SPFA

Bellman-Ford算法优化:

dist[b] = min(dist[b],backup[a] + w);

只有a变小了,b才会变小,那么在这里进行BFS

image-20210906203859163

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-09-07 11:04:32  更:2021-09-07 11:05:17 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 1:42:47-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码