基礎算法
前綴和
一維
#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;
}