基础算法
前缀和
一维
#include<iostream>
using namespace std;
const int N = 1e6+10;
int q[N],s[N];
int n,m;
int main()
{
ios::sync_with_stdio(false);//让cin与标准的输入不同步,提供cin的读取速度!
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>q[i];
for(int i=1;i<=n;i++) s[i]=s[i-1]+q[i];
while (m -- ){
int x,y;
cin>>x>>y;
cout<<s[y]-s[x-1]<<endl;
}
}
二维
#include<iostream>
using namespace std;
int n,m,q;
const int N =1010;
int a[N][N],s[N][N];
int main()
{
cin>>n>>m>>q;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
cin>>a[i][j];
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
}
while(q--)
{
int x1,y1,x2,y2;
cin>>x1>>y1>>x2>>y2;
cout<<s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]<<endl;
}
}
差分
一维
#include<iostream>
const int N = 100010;
using namespace std;
int l,r,c;
int n,m;
int a[N],b[N];
void insert(int l,int r,int c){
b[l]+=c;
b[r+1]-=c;
}
int main()
{
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) insert(i,i,a[i]);
while (m -- ){
cin>>l>>r>>c;
insert(l,r,c);
}
for(int i=1;i<=n;i++) b[i]+=b[i-1];
for(int i=1;i<=n;i++) cout<<b[i]<<" ";
}
二维
#include<iostream>
using namespace std;
const int N=1010;
int m,n,q;
int a[N][N],b[N][N];
int x1,y1,x2,y2,c;
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()
{
ios::sync_with_stdio(false);
cin>>n>>m>>q;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>a[i][j];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
insert(i,j,i,j,a[i][j]);
while(q--)
{
cin>>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++){
b[i][j]+=b[i-1][j]+b[i][j-1]-b[i-1][j-1];
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cout<<b[i][j]<<" ";
}
cout<<endl;
}
}
二分
整数的二分
#include<iostream>
using namespace std;
const int N=1e5+10;
int n,k,q;
int a[N];
int main()
{
ios::sync_with_stdio(false);
cin>>n>>q;
for(int i=0;i<n;i++) cin>>a[i];
while(q--){
int l=0,r=n-1;
cin>>k;
while(l<r){
int mid=l+r>>1;
if(a[mid]>=k) r=mid;
else l=mid+1;
}
if(a[l]!=k) cout<<"-1 -1"<<endl;
else{
cout<<l<<" ";
l=0,r=n-1;
while(l<r){
int mid=l+r+1>>1;
if(a[mid]<=k) l=mid;
else r=mid-1;
}
cout<<l<<endl;
}
}
}
浮点数的二分
#include<iostream>
using namespace std;
int main(){
double n;
cin>>n;
double l=-100;
double r=100;
while(r-l>1e-8){
double mid=(l+r)/2;
if(mid*mid*mid>=n) r=mid;
else l=mid;
}
cout<<l<<endl;
}
离散化+前缀和
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N=300010;
int a[N],s[N];
typedef pair<int,int> PII;
vector<int>alls;
vector<PII>add,query;
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 n,m;
int main(){
cin>>n>>m;
for(int i=0;i<n;i++){
int x,c;
cin>>x>>c;
alls.push_back(x);
add.push_back({x,c});
}
while(m--){
int l,r;
cin>>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);
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);
cout<<s[r]-s[l-1]<<endl;
}
}
vector<int>::iterator unique(vector<int> &a){
//双指针用法
for(int i=0,j=0;i<a.size();i++)
if(!i||a[i]!=a[i-1])//check判定条件
{
a[j]=a[i];
j++;
}
return a.begin()+j;
}
区间合并
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
typedef pair<int,int>PII;
int n;
void merge(vector<PII> &se){
vector<PII>res;//存不重合的区间个数,最后更新一下即可
sort(se.begin(),se.end());//以左端点进行排序
int st=-2e9,ed=-2e9;
for(auto s:se)
if(ed<s.first){//两种情况 ,无交集和有交集
if(st!=-2e9) res.push_back({st,ed});
st=s.first,ed=s.second;
}
else ed=max(ed,s.second);//有交集
if(st!=-2e9) res.push_back({st,ed});//最后一个区间放入res中
se=res;//更新一下
}
int main(){
vector<PII>se;
ios::sync_with_stdio(false);
cin>>n;
for(int i=0;i<n;i++){
int l,r;
cin>>l>>r;
se.push_back({l,r});
}
merge(se);
cout<<se.size()<<endl;
return 0;
}
基础数据结构
模拟栈
// tt表示栈顶
int stk[N], tt = 0;
// 向栈顶插入一个数
stk[ ++ tt] = x;
// 从栈顶弹出一个数
tt -- ;
// 栈顶的值
stk[tt];
// 判断栈是否为空
if (tt > 0)
{
}
模拟队列
//普通队列
// hh 表示队头,tt表示队尾
int q[N], hh = 0, tt = -1;
// 向队尾插入一个数
q[ ++ tt] = x;
// 从队头弹出一个数
hh ++ ;
// 队头的值
q[hh];
// 判断队列是否为空
if (hh <= tt)
{
}
// 循环队列
// hh 表示队头,tt表示队尾的后一个位置
int q[N], hh = 0, tt = 0;
// 向队尾插入一个数
q[tt ++ ] = x;
if (tt == N) tt = 0;
// 从队头弹出一个数
hh ++ ;
if (hh == N) hh = 0;
// 队头的值
q[hh];
// 判断队列是否为空
if (hh != tt)
{
}
单调栈
#include<iostream>
using namespace std;
const int N=10010;
int n;
int stk[N],tt;
int main(){
cin.tie(0);
cin>>n;
while(n--){
int x;
cin>>x;
while(tt&&stk[tt]>=x) tt--;
if(tt) cout<<stk[tt]<<" ";
else cout<<"-1"<<" ";
stk[++tt]=x;
}
}
#include<iostream>
#include<stack>
using namespace std;
int n;
stack<int>skt;
int main(){
cin.tie(0);
cin>>n;
for(int i=0;i<n;i++)
{
int x;
cin>>x;
while(!skt.empty()&&skt.top()>=x) skt.pop();
if(!skt.empty()) cout<<skt.top()<<" ";
else cout<<"-1"<<" ";
skt.push(x);
}
}
单调队列 滑动窗口
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e6+10;
int n,k;
int a[N],q[N];
int main()
{
//min
scanf("%d%d", &n,&k);
for (int i = 0; i < n; i ++ ) cin>>a[i];
int 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]]);
}
puts("");
//max
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]]);
}
puts("");
}
KMP
朴素匹配算法
//KMP朴素匹配算法
for(int i=1;i<=n;i++)
bool flag=true;
for(int j=1;j<=m;j++)
{
if(s[i]!=p[j])
{
flag=false;
break;
}
}
KMP字符串
理解起来很抽象,好不容易理解清楚,后面还会多写几次
#include<iostream>
using namespace std;
const int N=100010,M=1000010;
int n,m;
char p[N],s[M];
int ne[N];
int main(){
cin>>n>>p+1>>m>>s+1;
//求next
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) {
printf("%d ",i-n);
j=ne[j];
}
}
return 0;
}
trie
概念
trie是一种具有存储和查询功能字符串的数据结构
acwing 835 字符串统计
题目链接
对应代码的son[p][u]代表存储着某个节点的子节点序号,树中的节点是一个一个分配的,idx用来表示当前分配到了第几个位置,因为题目只针对小写英文字母,所以一个节点的子节点最多26个,cnt[]用来表示当前节点出现的次数,不理解的自己手动进行模拟一下就清楚了
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 10010;
int son[N][26],idx,cnt[N];
char str[N];
void insert(char *str){
int p=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;
cin>>n;
while(n--){
char c;
cin>>c>>str;
if(c=='I') insert(str);
else cout<<query(str)<<endl;
}
return 0;
}
详解
并查集
优化
路径压缩
按秩合并
并查集+路径压缩
简单并查集
#include <iostream>
#include <cstring>
#include <algorithm>
const int N = 1e5+10;
using namespace std;
int n,m;
int p[N];
int find(int x) // 并查集+路径压缩
{
//不是根节点
if(p[x]!=x) p[x]=find(p[x]);//路径压缩让父节点指向根节点
return p[x];
}
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++) p[i]=i;//初始化
while (m -- ){
char op[2];
int a,b;
scanf("%s%d%d", op,&a,&b);
if(*op=='M') p[find(a)]=find(b); //a节点的祖宗节点的父节点指向b的祖宗节点
else if(*op=='Q'){
if(find(a)==find(b))
puts("Yes");
else puts("No");
}
}
}
存每个点的个数的并查集+路径压缩
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
int n,m,a,b;
int p[N],cnt[N];
int find(int x) // 并查集
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int UN(int a,int b){
cnt[find(b)]+=cnt[find(a)];
p[find(a)]=find(b);
}
void QY(int a,int b){
if(find(a)==find(b)) puts("Yes");
else puts("No");
}
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++)
{
cnt[i]=1;
p[i]=i;
}
while (m -- ){
string s;
cin>>s;
if(s=="C"){
cin>>a>>b;
if(find(a)==find(b)) continue;
UN(a,b);
}
else if(s=="Q1"){
cin>>a>>b;
QY(a,b);
}
else{
cin>>a;
cout<<cnt[find(a)]<<endl;
}
}
}
思路
图片为截取!后面忘了方便回顾。
哈希表
特殊哈希表 离散化
一般哈希表 开放地址法和拉链法
#include<iostream>
#include<cstring>
using namespace std;
const int N=200003,INF=0x3f3f3f3f;
int h[N],x;
int find(int x){
int k=(x%N+N)%N;
while(h[k]!=INF&&h[k]!=x)
{
k++;
if(k==N) k=0;
}
return k;
}
int main(){
int n;
cin>>n;
memset(h, 0x3f, sizeof h);
while(n--){
char op[2];
scanf("%s%d",op,&x);
if(*op=='I'){
h[find(x)]=x;
}
else {
if(h[find(x)]!=INF) puts("Yes");
else puts("No");
}
}
}
字符串哈希
主要用于解决一个字符串中 某两个区间是否存在相同的字符串或者输出某个字符串区间对应的hash值
#include <iostream>
#include <cstring>
#include <algorithm>
const int N = 1e5+10;
using namespace std;
typedef unsigned long long ULL;
ULL h[N],p[N];
int P=131,n,m;
char str[N];
int get(int l,int r){
return h[r]-h[l-1]*p[r-l+1];
}
int main()
{
cin.tie(0);
cin>>n>>m;
cin>>str+1;
p[0]=1;
for(int i=1;i<=n;i++){
p[i]=p[i-1]*P;
h[i]=h[i-1]*P+str[i];
}
while (m -- ){
int l1,r1,l2,r2;
cin>>l1>>r1>>l2>>r2;
if(get(l1,r1)==get(l2,r2)) puts("Yes");
else puts("No");
}
}
思路
冲突问题:P=131,13331时,Q=2的64次方时,可99.9%避免冲突问题,可以用unsigned long long 代替Q 2的64 溢出自动取模
STL
vector(变长数组),倍增的思想,支持比较运算(按字典序)
#### 支持比较运算
比如:vector<int>a(4,3),b(3,4) if(a<b) puts("yes") 3333<4444 按字典序比较
定义::
vector <int> a; 定义:一个vector数组a
vector <int> a(10); 定义:一个长度为10的vector数组a
vector <int> a(10,3); 定义:一个长度为10的vector数组a,并且所有元素都为3
常用函数::
size(); 返回元素个数
empty(); 返回是否是空
clear(); 清空
front(); 返回vector的第一个数
back(); 返回vector的最后一个数
push_back(); 向vector的最后插入一个数
pop_back(); 把vector的最后一个数删掉
begin(); vector的第0个数
end(); vector的最后一个的数的后面一个数
倍增的思想:
系统为某一程序分配空间是,所需时间,与空间大小无关,与申请次数有关
遍历方法
假设有个vector <int> a;
第一种:
for(int i = 0;i < a.size();i ++) cout<<a[i]<<" ";
第二种:
for(vector <int>::iterator i = a.begin();i != a.end();i ++) cout<<*i<<" "; vector <int>::iterator可以写为auto
第三种:
for(auto x : a) cout<<x<<" ";
pair,支持比较运算,以first为第一关键字,以second为第二关键字(按字典序)
定义::
pair <类型,类型> 变量名; 两个类型可以不同
初始化方式:
假设有个pair <int,string> p;
第一种:
p = make_pair(10,"abc");
第二种:
p = {10,"abc");
常用函数::
first(); 第一个元素
second(); 第二个元素
string(字符串)
常用函数::
substr(起始位置,长度(长度可省略)); 返回每一个子串
c_str(); 返回这个string对应的字符数组的头指针
size(); 返回字母个数
length(); 返回字母个数
empty(); 返回字符串是否为空
clear(); 把字符串清空
queue(队列)
定义::
queue <类型> 变量名;
常用函数::
size(); 这个队列的长度
empty(); 返回这个队列是否为空
push(); 往队尾插入一个元素
front(); 返回队头元素
back(); 返回队尾元素
pop(); 把队头弹出
注意:队列没有clear函数!!!
清空:
变量名 = queue <int> ();
priority_queue(优先队列,堆)
注意:默认是大根堆!!!
定义::
大根堆:priority_queue <类型> 变量名;
小根堆:(1)priority_queue <类型,vecotr <类型>,greater <类型>> 变量名
(2)大根堆插入的时候插入相反数
常用函数:
size(); 这个堆的长度
empty(); 返回这个堆是否为空
push();往堆里插入一个元素
top(); 返回堆顶元素
pop(); 弹出堆顶元素
注意:堆没有clear函数!!!
stack(栈)
常用函数:
size(); 这个栈的长度
empty(); 返回这个栈是否为空
push(); 向栈顶插入一个元素
top(); 返回栈顶元素
pop(); 弹出栈顶元素
deque(双端队列)
常用函数:
size(); 这个双端队列的长度
empty(); 返回这个双端队列是否为空
clear(); 清空这个双端队列
front(); 返回第一个元素
back(); 返回最后一个元素
push_back(); 向最后插入一个元素
pop_back(); 弹出最后一个元素
push_front(); 向队首插入一个元素
pop_front(); 弹出第一个元素
begin(); 双端队列的第0个数
end(); 双端队列的最后一个的数的后面一个数
set,map,multiset,multimap 基于平衡二叉树(红黑树),动态维护有序序列
set/multiset
注意:set不允许元素重复,如果有重复就会被忽略,但multiset允许!!!
常用函数:
size(); 返回元素个数
empty(); 返回set是否是空的
clear(); 清空
begin(); 第0个数,支持++或--,返回前驱和后继
end(); 最后一个的数的后面一个数,支持++或--,返回前驱和后继
insert(); 插入一个数
find(); 查找一个数
count(); 返回某一个数的个数
erase();
(1)输入是一个数x,删除所有x O(k + log n) k==x的个数
(2)输入一个迭代器,删除这个迭代器
lower_bound(x); 返回大于等于x的最小的数的迭代器
upper_bound(x); 返回大于x的最小的数的迭代器
map/multimap
常用函数:
insert(); 插入一个数,插入的数是一个pair
erase();
(1)输入是pair
(2)输入一个迭代器,删除这个迭代器
find(); 查找一个数
lower_bound(x); 返回大于等于x的最小的数的迭代器
upper_bound(x); 返回大于x的最小的数的迭代器
unordered_set,unordered_map,unordered_muliset,unordered_multimap 基于哈希表
和上面类似,增删改查的时间复杂度是O(1)
不支持lower_bound()和upper_bound()
bitset 压位
定义:
bitset <个数> 变量名;
支持:
~,&,|,^
>>,<<
==,!=
[]
常用函数:
count(); 返回某一个数的个数
any(); 判断是否至少有一个1
none(); 判断是否全为0
set(); 把所有位置赋值为1
set(k,v); 将第k位变成v
reset(); 把所有位变成0
flip(); 把所有位取反,等价于~
flip(k); 把第k位取反
数论
质数
判断素数
最好不写成i<=sqrt(n)这种方式,每次求sqrt时间复杂度增加,i*i<=n会有溢出问题
// 判断质数
普通写法:时间复杂度O(n)
bool is_prime(int n){
if(n<2) return false;
for(int i=2;i<n;i++){
if(n%i==0) return false;
}
return true;
}
优化:时间复杂度O(sqrt(n))
bool is_prime(int n){
if(n<2) return false;
for(int i=2;i<=n/i;i++)
{
if(n%i==0) return false;
}
return true;
}
分解质因数
// 分解质因数
void divide(int x){
for(int i=2;i<=n;i++){
if(n%i==0) //i一定是质数
int s=0;
while(n%i==0){
n/=i;
s++;
}
printf("%d %d\n",i,s);
}
}
优化:n中最多只包含一个大于\(\sqrt{n}\)的质因子,所以枚举的时候可以先把所有小于sqrt(n)的质因子枚举出来,如果最后n>1,说明剩下的这个数就是大于sqrt(n)的质因子 ,输出n和次数1 这样复杂度就可以有效的从O(n)降低到O(sqrt(n)) 最快O(log(n)) 最慢O(sqrt(n))
void divide(int n){
for(int i=2;i<=n/i;i++){
if(n%i==0){
int s=0;
while(n%i==0){
n/=i;
s++;
}
printf("%d %d\n",i,s);
}
}
if(n>1) printf("%d %d\n",n,1);
puts("");
}
筛质数
朴素
朴素筛法:对大于2的每一个数都进行倍数删除,剩下的数就是素数,但大概时间复杂度为O(n)!
步骤:把 [2,n−1][2,n−1] 中的所有的数的倍数都标记上,最后没有被标记的数就是质数.
原理:假定有一个数p未被 [2,p−1][2,p−1] 中的数标记过,那么说明,不存在 [2,p−1][2,p−1] 中的任何一个数的倍数是p,也就是说 [2,p−1][2,p−1] 中不存在p的约数,因此,根据质数的定义可知p是质数.
调和级数:当n趋近于正无穷的时候,1/2+1/3+1/4+1/5+…+1/n=lnn+c1/2+1/3+1/4+1/5+…+1/n=lnn+c( cc 是欧拉常数,约等于 0.5770.577 左右)
时间复杂度:约为 O(nlogn)(注:此处的 loglog 特指以2为底)
void get_prime(int n){
for(int i=2;i<=n;i++){
if(!st[i]) prime[cnt++]=i;//把没有被删的数放进去,说明它是质数
for(int j=i+i;j<=n;j+=i) st[j]=true; //删除翻倍的i
}
}
埃氏筛法
埃氏筛法,时间复杂度(O(nloglogn)),比普通筛法快3倍左右。
质数唯一分解定理:每个大于1的自然数,要么本身就是质数,要么可以写为2个或以上的质数的积
优化:每一次只对素数进行的倍数进行删除,当一个数不是质数的时候,就不需要进行倍数删除(因为质数唯一分解定理)
质数定理:1~n1~n 中有n/lnn个质数.
步骤:在朴素筛法的过程中只用质数项去筛.
时间复杂度:O(nlog(logn))。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e6+10;
int prime[N],n,cnt;
bool st[N];
void get_prime(int n){
for(int i=2;i<=n;i++){
if(!st[i]) {
prime[cnt++]=i;
for(int j=i+i;j<=n;j+=i) st[j]=true; //每一次只删除素数的倍数
}
}
}
int main()
{
cin>>n;
get_prime(n);
cout<<cnt<<endl;
}
线性筛法
若n≈106,线性筛和埃氏筛的时间效率差不多,若 n≈107,线性筛会比埃氏筛快了大概一倍。
核心:1~n1~n 内的合数 p只会被其最小质因子筛掉。(算数基本定理)
原理:1~n1~n 之内的任何一个合数一定会被筛掉,而且筛的时候只用最小质因子来筛,然后每一个数都只有一个最小质因子,因此每个数都只会被筛一次,因此线性筛法是线性的.
枚举到 i 的最小质因子的时候就会停下来,即 if (i % primes[j] == 0) break
当i%primes[j] != 0时,primes[j]一定小于i的最小质因子,primes[j]一定是primes[j]*i的最小质因子
当i% primes[j] == 0 时,primes[j] 一定是i的最小质因子,而primes[j]又是primes[j]的最小质因子,因此 primes[j]是primes[j]*i的最小质因子
int primes[N], cnt; // primes[]存储所有素数,cnt为质数的个数
bool st[N]; // st[x]存储x是否被筛掉
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (!st[i]) primes[cnt ++ ] = i;
// j < cnt 不必要,因为 primes[cnt - 1] = 当前最大质数
// 如果 i 不是质数,肯定会在中间就 break 掉
// 如果 i 是质数,那么 primes[cnt - 1] = i,也保证了 j < cnt
for (int j = 0; primes[j] <= n / i; j ++ )
{
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
约数
求所有的约数
#include<iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<int>get_num(int x){
vector<int>res;
for(int i=1;i<=x/i;i++){
if(x%i==0){
res.push_back(i);
if(i!=x/i) res.push_back(x/i);
}
}
sort(res.begin(),res.end());
return res;
}
int main()
{
int n;
cin>>n;
while (n -- ){
int x;
cin>>x;
auto num=get_num(x);
for(auto it:num){
cout<<it<<" ";
}
puts("");
}
}
约数个数
约数个数和约数之和是一类题,不能用求所有约数的试除法那样,会有数据溢出问题,只需要记住并理解相应的公式即可
约数个数:先求每个数的分解质因数,将底数和指数放在一个unordered_map中,然后开一个long long res=1;然后按照公式即可
约数之和: p为底数,上标为指数,
可以考虑等比数列求和公式,但是会有溢出问题 sum=a1*(1-q^n)/1-q
#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_map>
typedef long long LL;
using namespace std;
const int mod=1e9+7;
int main()
{
int n;
cin>>n;
unordered_map<int,int>q;
while (n -- ){
int x;
cin>>x;
for(int i=2;i<=x/i;i++){
while(x%i==0){
x/=i;
q[i]++;
}
}
if(x>1) q[x]++;
}
LL res=1;
for(auto it:q) res=res*(it.second+1)%mod;
cout<<res<<endl;
return 0;
}
约数之和
公式:sum=(\(p_0\)+....+\(p_a1\))(\(p_0\)+....+\(p_a2\))...*(\(p_0\)+....+\(p_ak\))
#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_map>
typedef long long LL;
using namespace std;
const int mod=1e9+7;
int main()
{
int n;
cin>>n;
unordered_map<int,int>q;
while (n -- ){
int x;
cin>>x;
for(int i=2;i<=x/i;i++){
while(x%i==0){
x/=i;
q[i]++;
}
}
if(x>1) q[x]++;
}
LL res=1;
for(auto s:q){
int x=s.first; //x为底数
int y=s.second; //y为指数
LL t=1;
while(y--) t=(t*x+1)%mod;
res=res*t%mod;
}
cout<<res<<endl;
return 0;
}
GCD最大公约数
适用整数,时间复杂度: O(n),n 为最大数字位数
int gcd(int a,int b){
return b?gcd(b,a%b),a;
#include<iostream>
int gcd(int a,int b) {
return __gcd(a,b);
}
适用于小数,EPS 为精度控制
double gcd(double a,double b){
return a<EPS?b:gcd(fmod(b,a),a);
}
欧拉函数
单数欧拉
y(n):1到n中与n互质的数
以下为求的公式以及推理(容斥原理)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int n;
int main()
{
cin>>n;
while (n -- ){
int x;
cin>>x;
int res=x;
for(int i=2;i<=x/i;i++){
if(x%i==0){
res=res/i*(i-1);//处理小数问题
while(x%i==0){
x/=i;
}
}
}
if(x>1) res=res/x*(x-1);
cout<<res<<endl;
}
return 0;
}
线性筛法
适用于求1~n欧拉函数之和
phi[N]为欧拉函数
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=1000010;
int primes[N],cnt;
bool st[N];
int phi[N];//欧拉函数
LL get_euler(int n){
phi[1]=1;
for(int i=2;i<=n;i++){
if(!st[i]) {
primes[cnt++]=i;
phi[i]=i-1;
}
for(int j=0;primes[j]<=n/i;j++){
st[primes[j]*i]=true;
if(i%primes[j]==0){
phi[primes[j]*i]=primes[j]*phi[i];
break;
}
else{
phi[primes[j]*i]=phi[i]*(primes[j]-1);
}
}
}
LL res=0;
for(int i=1;i<=n;i++){
res+=phi[i];
}
return res;
}
int main(){
int n;
cin>>n;
cout<<get_euler(n)<<endl;
return 0;
}
欧拉定理
若a与n互质
a y(n)=1(mod p)
费马定理
当n为质数的时候
y(n)=n-1
a n-1=1(mod p)
高斯消元
高次同余方程
容斥原理
中国剩余定理
扩展欧几里得定理
排列组合
特殊数之卡特兰数
快速幂
康拓扩展
莫比乌斯反演
博弈论
图论
DFS
回溯剪枝
//DFS(回溯和剪枝)
剪枝:1.当当前的路径一定可以判断不如最优解的时候,就可以剪掉。
2.可行性剪枝,当当前的路径不合法就可以剪枝
n皇后问题(遍历行)
时间复杂度:O(n^2)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 20;
int col[N],dg[N],udg[N];
int n;
char g[N][N];
void dfs(int u){
if(u==n){
for (int i = 0; i < n; i ++ ) puts(g[i]);
puts("");
return;
}
for (int i = 0; i < n; i ++ ){
if(!col[i]&&!dg[u+i]&&!udg[n-u+i]){
g[u][i]='Q';
col[i]=dg[u+i]=udg[n-u+i]=1;
dfs(u+1);
col[i]=dg[u+i]=udg[n-u+i]=0;
g[u][i]='.';
}
}
}
int main()
{
cin>>n;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ )
g[i][j]='.';
dfs(0);
return 0;
}
n皇后问题(遍历列)
最直接的做法,时间复杂度为O(2^n)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 20;
int col[N],row[N],dg[N],udg[N];
int n;
char g[N][N];
void dfs(int x,int y,int s){
if(y==n) y=0,x++;
if(x==n) //代表走到最后一行
{
if(s==n){//放了n个皇后才打印
for (int i = 0; i < n; i ++ ) puts(g[i]);
puts("");
}
return;
}
//不放皇后
dfs(x,y+1,s);
//放皇后
if(!row[x]&&!col[y]&&!dg[x+y]&&!udg[x-y+n]){
row[x]=col[y]=dg[x+y]=udg[x-y+n]=1;
g[x][y]='Q';
dfs(x,y+1,s+1);
row[x]=col[y]=dg[x+y]=udg[x-y+n]=0;
g[x][y]='.';
}
}
int main()
{
cin>>n;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ )
g[i][j]='.';
dfs(0,0,0);
return 0;
}
BFS
最短路
BFS主要用来搜最短路
走迷宫
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef pair<int, int> PII;
int n,m;
const int N = 110;
int g[N][N],d[N][N];//每个点到起点的距离
int bfs(){
queue<PII>q;
memset(d,-1,sizeof d);
q.push({0,0});
d[0][0]=0;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};//向上 向右 向下 向左 4方向偏移量
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)//d[x][y]=-1代表还没有走过
{//它已经走过的话,说明这个点不是第一次搜到,bfs是第一次搜到的点是最短距离,否则不是最短距离
d[x][y]=d[t.first][t.second]+1;
q.push({x,y});
}
}
}
return d[n-1][m-1];
}
int main()
{
cin >> n>>m;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < m; j ++ )
cin>>g[i][j];
cout<<bfs()<<endl;
}
存最短路径的坐标
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef pair<int, int> PII;
int n,m;
const int N = 110;
int g[N][N],d[N][N];//每个点到起点的距离
PII pre[N][N];//存每个路径
int bfs(){
queue<PII>q;
memset(d,-1,sizeof d);
q.push({0,0});
d[0][0]=0;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};//向上 向右 向下 向左 4方向偏移量
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)//d[x][y]=-1代表还没有走过
{//它已经走过的话,说明这个点不是第一次搜到,bfs是第一次搜到的点是最短距离,否则不是最短距离
d[x][y]=d[t.first][t.second]+1;
pre[x][y]=t;
q.push({x,y});
}
}
}
int x=n-1,y=m-1;
while(x||y)
{
cout<<x<<" "<<y<<" "<<endl;
auto t=pre[x][y];
x=t.first,y=t.second;
}
return d[n-1][m-1];
}
int main()
{
cin >> n>>m;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < m; j ++ )
cin>>g[i][j];
cout<<bfs()<<endl;
}
树与图的广度优先遍历
树与图的深度优先遍历
拓扑排序
Dijkstra算法
bellman-ford算法
SPFA算法
Floyd算法
Prim算法
Kruskal算法
二分图
匈牙利算法
动态规划dp
背包问题
01背包问题
特点:每件物品最多只用一次
背包:N个物品 V容量
每个物品体积Vi 权重WI
朴素
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int n,m;
int v[N],w[N];
int f[N][N];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
//初始化枚举所有的状态f[0-n][0-m], f[0][0-m]=0
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++){
//左边集合 不包含第i个物品 一定存在
f[i][j]=f[i-1][j];
//右边集合 不一定存在 当当前的j装不下vi的时候就是空集 j<vi
if(j>=v[i]){
f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
}
}
cout<<f[n][m]<<endl;
return 0;
}
一维优化(滚动数组)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int n,m;
int v[N],w[N];
int f[N];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
// for(int i=1;i<=n;i++)
// for(int j=v[i];j<=m;j++){
// // 当j<vi的时候是没有意义的 if(j>=v[i]可以去掉)
// f[j]=max(f[j],f[j-v[i]]+w[i]); //这一步j-v[i]是严格小于j的 他会在第i层j之前被计算 表示的就是i f[i][j-v[i]]+w[i]
// }
for(int i=1;i<=n;i++)
for(int j=m;j>=v[i];j--){ //只需要每次从m往前枚举 就可以避免j-v[i]被计算
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
cout<<f[m]<<endl;
return 0;
}
完全背包问题
朴素
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N][N];
int n,m;
int v[N],w[N];
int main()
{
//朴素做法 时间复杂度最坏10^9次方 可以优化
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++){
for(int k=0;k*v[i]<=j;k++){
f[i][j]=max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k); //不选i和选i合并在一起 因为k=0 也满足不选i的写法
}
}
cout<<f[n][m]<<endl;
return 0;
}
二维优化
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N][N];
int n,m;
int v[N],w[N];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++){
f[i][j]=f[i-1][j];
if(j>=v[i]) f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
}
cout<<f[n][m]<<endl;
return 0;
}
一维优化
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N];
int n,m;
int v[N],w[N];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)
for(int j=v[i];j<=m;j++){
//一维每次用到的都是上一层的状态 这里不能倒着枚举 因为此时的i就代表i 不代表i-1
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
cout<<f[m]<<endl;
return 0;
}
多重背包问题
朴素
/*
f[i][j]=max(f[i-1[j-v[i]*k+w[i]*k) k=0,1,2,3,.....,s[i];
*/
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 101;
int v[N],w[N],s[N];
int f[N][N];
int n,m;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>v[i]>>w[i]>>s[i];
}
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++){
for(int k=0;k*v[i]<=j&&k<=s[i];k++){
f[i][j]=max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k);
}
}
cout<<f[n][m]<<endl;
}
二进制优化
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 13000;
int n,m;
int v[N],w[N];
int f[N];
int main()
{
cin>>n>>m;
int cnt=0;//分组的组别
for(int i=1;i<=n;i++){
int a,b,s;
cin>>a>>b>>s;
int k=1;//每一个组别的个数
while(k<=s){
cnt++;
v[cnt]=a*k;//整体的价值
w[cnt]=b*k;//整体的体积
s-=k;
k=k*2;
}
if(s>0){ //最后的C
cnt++;
v[cnt]=a*s;
w[cnt]=b*s;
}
}
n=cnt;
for(int i=1;i<=n;i++)
for(int j=m;j>=v[i];j--){
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
cout<<f[m]<<endl;
}
分组背包问题
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110;
int n,m;
int v[N][N],w[N][N],s[N];//表示第 i 个物品组的物品数量
int f[N];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++){
cin >> s[i];
for(int j=0;j<s[i];j++)
cin >> v[i][j]>>w[i][j];
}
for(int i=1;i<=n;i++) //遍历每一个组别
for(int j=m;j>=0;j--)
for(int k=0;k<s[i];k++)//遍历一个k个物品
{
if(j>=v[i][k]){
f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
}
}
cout<<f[m]<<endl;
}