Other
初始化二维vector
vector< vector<int> >g(n+1, vector<int>(n+1, 0));
初始化:
memset(a, 0/-1/INF,sizeof(a)); //初始化数组a,使其中值为0/-1/INF。
memcpy(b,a,sizeof(a)); // 将数组a中的值复制到新数组b中。
字符串:
string s;
s.substr(pos,len); // 获得字符串s从位置pos开始长度为len的子串。
s.push_back('c'); //在字符串s的尾部添加字符c
s.pop_back(); //弹出字符串s的最后一个元素
C语言的排序:
//对数组a[1]-a[100]排序
int cmp(const void *a, const void *b){
return *(int *)a - *(int *)b;
}
qsort(a+1, 100, sizeof(int), cmp);
//对结构体a[1]-a[100]排序
struct Node{
char s;
int cnt;
}a[105];
int cmp(const void *a, const void *b){
return (*(Node *)a).cnt - (*(Node *)b).cnt;
}
qsort(a+1, 100, sizeof(Node), cmp);
C语言的文件操作:
FILE *file= fopen(filename, "r"); //打开文件句柄
fclose(file); //关闭文件
1. ch=fgetc(file) //一次读一个文件中的字符
while((ch=fgetc(file))!=EOF) {} //读取文件中所有的字符,直至碰见了EOF
2. fputc('a', file); //一次写一个字符a到文件中
3. fputs("hello world!", file); //一次写一个字符串""到文件中(会覆盖原来的数据)
4. char arr[10]; fgets(arr, 10, file); //一次从文件中读5个字节的数据到数组arr中
5. char str[10]; int a=10;
fprintf(file, "%s %d\n", str, a); //将字符串和整数按照格式化的方式写入文件file中
6. fscanf(file, "%s %d\n", str, a); //将文件中的数据读入至str字符串和整数a中
while(fscanf(file, "%s", str)!=EOF){} //从文件中按照单词读取数据,遇见空格或者换行符则停止
后缀表达式求值:
#include<bits/stdc++.h>
using namespace std;
int main(){
stack<int>s;
string str; cin>>str;
int len=str.length(), temp=0;
for(int i=0; i<len; i++){
if(str[i]>='0' && str[i]<='9') temp=temp*10+(str[i]-'0');
else if(str[i]=='.'){
s.push(temp); temp=0;
}
else if(str[i]=='@') break;
else {
int se=s.top(); s.pop();
int fi=s.top(); s.pop();
if(str[i]=='+') s.push(fi+se);
else if(str[i]=='-') s.push(fi-se);
else if(str[i]=='/') s.push(fi/se);
else if(str[i]=='*') s.push(fi*se);
}
}
cout<<s.top()<<endl;
return 0;
}
中缀表达式转为后缀表达式
#include<bits/stdc++.h>
using namespace std;
string mid_str, pre_str;
int get_priority(char c){
if(c=='+' || c=='-') return 1;
else if(c=='*' || c=='/') return 2;
else return 0;
}
bool is_num(char ch){
if(ch>='0' && ch<='9') return true;
else return false;
}
string mid2pre_string(string str){
string postfix="";
stack<char>oper;
int len=str.length();
for(int i=0; i<len; i++){
char ch=str[i];
if(is_num(ch)) postfix+=ch; //如果是操作数
else if(ch=='(') oper.push(ch);
else if(ch==')'){
while(!oper.empty() && oper.top() != '('){
postfix += oper.top();
oper.pop();
}
oper.pop();
}
else {
while(!oper.empty() && get_priority(ch)<=get_priority(oper.top())){
postfix += oper.top();
oper.pop();
}
oper.push(ch);
}
}
while(!oper.empty()){
postfix += oper.top();
oper.pop();
}
return postfix;
}
int main(){
cin>>mid_str; //输入中缀表达式
pre_str=mid2pre_string(mid_str); // 中缀转为前缀
cout<<pre_str<<endl;
return 0;
}
格雷编码的算法公式
第 i (i≥0) 个格雷码即为:gi=i⊕⌊i/2⌋,向下取整。
数论方面
求整数n的不同质因数个数
方法:O(sqrt(n))
long long n;
int ans=0;
for(long long i=2; i*i<=n; i++){
if(n%i==0) ans++;
while(n%i==0) n/=i;
}
if(n!=1) ans++;
求质数的2种方法
方法一:埃及筛O(nlogn)
bool prime[maxn];//prime[i]为false代表i是质数
memset(prime,false,sizeof(prime));
prime[1]=true;
for(int i=2;i<=n;i++){
if(!prime[i]) {
for(int j=i+i;j<=n;j+=i) prime[j]=true;
}
}
方法二:欧拉筛O(n)
int prime[maxn],cnt=0;
bool vis[maxn];
memset(vis,false,sizeof(vis));
for(int i=2;i<=n;i++){
if(!vis[i]) prime[++cnt]=i;
for(int j=1;j<=cnt&&i*prime[j]<=n;j++){
vis[i*prime[j]]=true;
if(i%prime[j]==0) break;
}
}
最大公因数
inline int gcd(int a,int b){ return b?gcd(b,a%b):a; }
最小公倍数
inline int lcm(int a,int b){ return a/gcd(a,b)*b; }
费马小定理求逆元
inline int inv(int a,int mod){ return qpow(a,mod-2,mod)%mod; }
快速幂
int qpow(int a,int b,int mod){
int res=1;
while(b){
if(b&1) res=res*a%mod;
b>>=1;
a=a*a%mod;
}
return res;
}
快速乘
int qmul(int a,int b,int mod){
int res=0;
while(b){
if(b&1) res=(res+a)%mod;
b>>=1;
a=(a+a%mod);
}
return res;
}
二分 O(logn)
解决具有单调性的问题
形如:
int l=0,r=INF;
bool check(int x){
}
while(l<r){
int mid=l+r>>1;
if(check(mid)) l=mid;
else r=mid-1;
}
三分 O(logn)
解决非线性函数形状的问题(比如n次方函数,n>1)
int l=0,r=INF;
int cal(int x){
}
while(l<r){
int mid1=l+(r-l)/3;
int mid2=r-(r-l)/3;
if(cal(mid1)<cal(mid2)) l=mid1;
else r=mid2;
}
离散化
int a[maxn],lsh[maxn],cnt;
inline int get(int x){
return lower_bound(lsh+1,lsh+1+cnt,x)-lsh;
}
for(int i=1;i<=n;i++){
cin>>a[i]; lsh[i]=a[i];
}
sort(lsh+1,lsh+1+n);
cnt=unique(lsh+1,lhs+1+n)-lsh-1;
差分
特点:以O(1)的复杂度修改区间值
int s[maxn]; //差分数组
for(int i=1;i<=n;i++){
s[i]=a[i]-a[i-1];
}
//q次修改区间[l,r]
for(int i=1;i<=q;i++){
int l,r,x; cin>>l>>r>>x;
s[l]+=x; s[r+1]-=x;
}
博弈论
Nim博弈
Nim博弈定义:
有n 堆石子(n > 0),每一堆有ai (ai > 0, 1 <= i <= n)个石子。
每人每次可以从任意一堆石子里,取出任意多枚石子扔掉,可以取完,不能不取,每次只能从一堆里取。最后没有石子可以取的人输掉这场游戏。
设甲为先手,乙为后手,两个人以最佳策略进行操作。
给出n,和这n 堆石子分别的数量,请问是否存在先手必胜的策略?
结论:
如果这n堆石子的数量满足:
a1 xor a2 xor a3 xor … xor an = 0.
则先手必败,否则先手必胜。
字符串方面
字典树
应用一:查询n个数中选取两个数做异或得到的最大值
int trie[maxn][4],tot=0;
void Insert(int p){
//将数字p的二进制形式按照高位进行插入
int x=0;
for(int i=30;i>=0;i--){
int u=(p>>i)&1;
if(!trie[x][u]) trie[x][u]=++tot;
x=trie[x][u];
}
}
int Search(int p){
int res=0,x=0;
for(int i=30;i>=0;i--){
int u=(p>>i)&1;
if(trie[x][!u]){
res+=(1<<i);
x=trie[x][!u];
}
else x=trie[x][u];
}
return res;
}
应用二:查询n个字符串之间是否有前缀包含关系
int trie[maxn][14],tot=0,tag[maxn];
void Insert(char s[]){
int len=strlen(s),x=0;
for(int i=0;i<len;i++){
int y=s[i]-'0';
if(!trie[x][y]) trie[x][y]=++tot;
x=trie[x][y];
}
tag[x]++;
}
int Search(char s[]){
int x=0,len=strlen(s),res=0;
for(int i=0;i<len;i++){
int y=s[i]-'0';
if(!trie[x][y]) break;
x=trie[x][y];
if(i<len-1&&tag[x]) return 2;
}
return tag[x];
}
AC自动机
用来求解字符串多匹配问题,即n个模式串,1个文本串。主要有以下两类问题
- 求解文本串中出现了多少个模式串;
- 求解每个模式串在文本串中出现的次数。
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
int n,trie[maxn][30],tot=0;
//记录某个串的出现次数 //失败时的回溯指针
int cnt[maxn],fail[maxn*30];
char str[maxn]; //模式串
char s[maxn]; //文本串
//建立字典树
void Insert(char *s){
int x=0,len=strlen(s);
for(int i=0;i<len;i++){
int y=s[i]-'a';
if(!trie[x][y]) trie[x][y]=++tot;
x=trie[x][y];
}
cnt[x]++; //当前节点单词数+1
}
//给fail数组赋值
void getFail(){
queue<int>q;
for(int i=0;i<26;i++){
if(trie[0][i]) {
fail[trie[0][i]]=0;
q.push(trie[0][i]);
}
}
//fail[x] ->当前节点x的失败指针指向的地方
//tire[x][i] -> 下一个字母为i+'a'的节点的下标为tire[x][i]
while(!q.empty()){
int x=q.front(); q.pop();
for(int i=0;i<26;i++){
//如果有这个子节点为字母i+'a',则
//让这个节点的失败指针指向(((他父亲节点)的失败指针所指向的那个节点)的下一个节点)
if(trie[x][i]){
fail[trie[x][i]]=trie[fail[x]][i];
q.push(trie[x][i]);
}
//否则就让当前节点的这个子节点
//指向当前节点fail指针的这个子节点
else trie[x][i]=trie[fail[x]][i];
}
}
}
//查询字典树中的单词在字符串s中出现的次数
int Search(char *s){
int x=0,len=strlen(s),res=0;
for(int i=0;i<len;i++){
//遍历文本串
x=trie[x][s[i]-'a'];
//一直向下寻找,直到匹配失败(失败指针指向根或者当前节点已找过).
for(int j=x; j&&cnt[j]!=-1; j=fail[j]) {
res+=cnt[j];
cnt[j]=-1;
}
}
return res;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
int n; cin>>n;
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++){
cin>>str; Insert(str);
}
getFail(); //对建好的字典树初始化匹配失败指针数组fail[]
cin>>s;
cout<<Search(s)<<endl;
return 0;
}
回文自动机(PAM)
一个理解不了的算法。时间复杂度O(n)
fail[u]:点u后缀边指向的点
len[u]: 点u所表示的回文串长度
trie[u][ch]: 点u前后各增加一个字符ch得到的点
str[i]:是需要构造的串的第i位
cnt[i]:表示以i 结尾的回文串的个数
last:指向最长回文子串的右端点
1.求字符串str中以每一位字符结尾的回文串个数,即截至到当前字符的cnt[last]
#include <bits/stdc++.h>
using namespace std;
const int maxn=2e6+10;
int sz, last, r0, r1;
int trie[maxn][26], fail[maxn], len[maxn], cnt[maxn];
char str[maxn];
void Init() {
r0 = sz++, r1 = sz++; last = r1;
len[r0] = 0, fail[r0] = r1;
len[r1] = -1, fail[r1] = r1;
}
void insert(int ch, int idx) {
int u = last;
while (str[idx] != str[idx - len[u] - 1])u = fail[u];
if (!trie[u][ch]) {
int cur = ++sz, v = fail[u];
len[cur] = len[u] + 2;
for (; str[idx] != str[idx - len[v] - 1]; v = fail[v]);
fail[cur] = trie[v][ch]; trie[u][ch] = cur;
cnt[cur] = cnt[fail[cur]] + 1;
}
last = trie[u][ch];
}
//建立回文树
void build(char* str) {
int len = strlen(str);
for (int i = 0; i < len; i++) {
insert(str[i] - 'a' + 1, i);
printf("%d ", cnt[last]);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
Init();
cin>>str;
build(str);
return 0;
}
2.求所有回文子串s的出现的次数乘以这个子串的长度的最大值。
#include <bits/stdc++.h>
using namespace std;
const int maxn=2e6+10;
int sz, last, r0, r1;
int trie[maxn][26], fail[maxn], len[maxn], cnt[maxn];
LL ans=0;
char str[maxn];
void Init() {
r0 = sz++, r1 = sz++; last = r1;
len[r0] = 0, fail[r0] = r1;
len[r1] = -1, fail[r1] = r1;
}
void insert(int ch, int idx) {
int u = last;
while (str[idx] != str[idx - len[u] - 1])u = fail[u];
if (!trie[u][ch]) {
int cur = ++sz, v = fail[u];
len[cur] = len[u] + 2;
for (; str[idx] != str[idx - len[v] - 1]; v = fail[v]);
fail[cur] = trie[v][ch]; trie[u][ch] = cur;
/与上面不一致
}
last = trie[u][ch];
cnt[last]++;
/与上面不一致
}
//建立回文树
void build(char* str) {
int len = strlen(str);
for (int i = 0; i < len; i++) {
insert(str[i] - 'a' + 1, i);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
Init();
cin>>str;
build(str);
for(int i=sz;i>=0;i--){
cnt[fail[i]]+=cnt[i];
ans=max(ans,1LL*cnt[i]*len[i]);
}
cout<<ans<<endl;
return 0;
}
字符串匹配的两种算法
暴力算法O(m*n)
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e4+10;
int main(){
char str1[maxn], str2[maxn];
cin>>str1>>str2;
int i=0, j=0, len1=strlen(str1), len2=strlen(str2);
while(i<len1 && j<len2){
if(str1[i]==str2[j]){
++i; ++j;
}
else {
i=i-j+2; j=1;
}
}
if(j>=len2) cout<<i-j<<endl;
else cout<<-1<<endl;
return 0;
}
KMP算法O(m+n)
图论方面
并查集
int fa[maxn];
void Init(){
for(int i=1;i<=n;i++) fa[i]=i;
}
inline int find(int x){
return fa[x]==x?fa[x]:fa[x]=find(fa[x]);
}
inline void Union(int u,int v){
int x=find(u),y=find(v);
if(x!=y) fa[x]=y;
}
最小生成树(MST)的两种算法
Kruskal算法
Kruskal算法O(m*logm)
int fa[N],cnt=0,ans=0;
void Init(){
for(int i=1;i<=n;i++) fa[i]=i;
}
inline int find(int x){
return fa[x]==x?fa[x]:fa[x]=find(fa[x]);
}
inline void Union(int u,int v){
int x=find(u),y=find(v);
if(x!=y) fa[x]=y;
}
struct Edge{
int u,v,w;
bool operator < (const Edge&b) const{
return w<b.w;
}
}e[maxn];
void Kruskal(){
sort(e+1,e+1+m);
Init();
for(int i=1;i<=m;i++){
int u=e[i].u,v=e[i].v,w=e[i].w;
int x=find(u),y=find(v);
if(x!=y){
ans+=w; fa[x]=y; cnt++;
}
}
if(cnt!=n-1) cout<<"不能生成MST"<<endl;
else cout<<ans<<endl;
}
Prim算法
暴力做法 时间复杂度O(n*n)
//与Dijkstra算法很相似,唯一的不同是松弛权值部分
int n,m,s[N][N],d[N],ans=0;
bool vis[N];
void Prim(){
memset(s,INF,sizeof(s));
memset(vis,false,sizeof(vis));
for(int i=1;i<=m;i++){
int u,v,w; cin>>u>>v>>w;
s[u][v]=s[v][u]=min(w,s[u][v]);
}
for(int i=1;i<=n;i++) d[i]=s[1][i];
d[1]=0; vis[1]=true;
for(int i=1;i<n;i++){
int minn=INF,pos;
for(int j=1;j<=n;j++){
if(!vis[j]&&d[j]<minn){
minn=d[j]; pos=j;
}
}
ans+=minn; vis[pos]=true;
for(int j=1;j<=n;j++) d[j]=min(d[j],s[pos][j]);
}
bool flag=true;
for(int i=1;i<=n;i++){
if(!vis[i]) { flag=false; break; }
}
if(!flag) cout<<"不能生成MST"<<endl;
else cout<<ans<<endl;
}
通过优先队列进行优化O(n*logn)
int n,m,d[N],ans=0;
bool vis[N];
vector<pair<int,int> >g[N];
void Prim(){
memset(vis,false,sizeof(vis));
memset(d,INF,sizeof(d));
for(int i=1;i<=m;i++){
int u,v,w; cin>>u>>v>>w;
g[u].push_back({v,w});
g[v].push_back({u,w});
}
priority_queue<pair<int,int> >q;
q.push({1,0});
while(!q.empty()){
pair<int,int>p=q.top(); q.pop();
int u=p.first;
if(vis[u]) continue;
vis[u]=true; ans+=p.second;
int len=g[u].size();
for(int i=0;i<len;i++){
int v=g[u][i].first,w=g[u][i].second;
if(d[v]>w){
d[v]=w;
q.push({v,w});
}
}
}
bool flag=true;
for(int i=1;i<=n;i++){
if(!vis[i]) { flag=false; break; }
}
if(!flag) cout<<"不能生成MST"<<endl;
else cout<<ans<<endl;
}
拓扑排序
暴力做法O(nm)
int in[maxn],topu[maxn],tot=0;
for(int i=1;i<=n;i++){
int pos;
for(int j=1;j<=n;j++){
if(in[j]==0) {
pos=j; topu[++tot]=j;
in[j]--;
break;
}
}
for(int j=1;j<=n;j++){
if(e[pos][j]) in[j]--;
}
}
队列优化O(n+m)
vector<int>g[maxn],topu;
int in[maxn];
queue<int>q;
for(int i=1;i<=n;i++){
if(in[i]==0) q.push(i);
}
while(!q.empty()){
if(q.size()>1) cout<<"该拓扑排序的排序规则不唯一"<<endl;
int x=q.front(); q.pop();
topu.emplace_back(x);
for(auto it:g[x]){
in[it]--;
if(in[it]==0) q.push(it);
}
}
最短路的四种算法
Floyed算法
多源最短路O(n^3)
可以处理负权和负权边,但是不能判断负权回路。
int d[maxn][maxn];
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++) d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
}
Dijkstra算法
单源最短路–暴力做法O(n^2)
不能处理负权和负权边,也不能判断负权回路。
int d[maxn],e[maxn][maxn];
bool vis[maxn];
void Dijkstra(int s){
memset(e,INF,sizeof(e));
memset(vis,false,sizeof(vis));
for(int i=1;i<=m;i++){
cin>>u>>v>>w;
e[u][v]=min(w,e[u][v]);
}
for(int i=1;i<=n;i++) d[i]=e[s][i];
d[s]=0; vis[s]=true;
int minn=INF,pos;
for(int i=1;i<n;i++){
for(int j=1;j<=n;j++){
if(!vis[j]&&d[j]<minn) minn=d[j],pos=j;
}
vis[pos]=true;
for(int j=1;j<=n;j++) {
if(d[j]>d[pos]+e[pos][j]) d[j]=d[pos]+e[pos][j];
}
}
}
单源最短路–通过优先队列进行优化O(mlogn)
int d[maxn];
struct Edge{
int v,w;
bool operator < (const Edge&b) const{
return w>b.w;
}
};
vector<Edge>g[maxn];
bool vis[maxn];
void Dijkstra(int s){
memset(vis,false,sizeof(vis));
for(int i=1;i<=m;i++){
cin>>u>>v>>w;
g[u].push_back({v,w});
}
memset(d,INF,sizeof(d));
d[s]=0;
priority_queue<Edge>q;
q.push({s,0});
while(!q.empty()){
Edge p=q.top(); q.pop();
int u=p.v;
if(vis[u]) continue;
vis[u]=true;
for(auto it:g[u]){
int v=it.v,w=it.w;
if(d[v]>d[u]+w) {
d[v]=d[u]+w;
q.push({v,d[v]});
}
}
}
}
Bellman-Ford算法
单源最短路O(nm)
可以处理负权和负权边,也可以判断负权回路。还可以处理有边数限制的最短路问题。
无边数限制,求最短路
int n,m,k,d[N];
struct Edge{
int u,v,w;
}e[maxn];
void Bellman_ford(int s){
for(int i=1;i<=m;i++) cin>>e[i].u>>e[i].v>>e[i].w;
memset(d,INF,sizeof(d));
d[s]=0;
for(int i=1;i<=n-1;i++){
bool flag=true; //判断每一轮是否进行了松弛
for(int j=1;j<=m;j++){
int u=e[j].u,v=e[j].v,w=e[j].w;
if(d[v]>d[u]+w){
d[v]=d[u]+w;
flag=false;
}
}
if(flag) break; //若没有进行过松弛,则提前跳出
}
}
有边数k限制,求最短路
int n,m,k,d[N],book[N];
struct Edge{
int u,v,w;
}e[maxn];
void Bellman_ford(int s){
for(int i=1;i<=m;i++) cin>>e[i].u>>e[i].v>>e[i].w;
memset(d,INF,sizeof(d));
d[s]=0;
for(int i=1;i<=k;i++){
bool flag=true; //判断每一轮是否进行了松弛
memcpy(book,d,sizeof(d));
for(int j=1;j<=m;j++){
int u=e[j].u,v=e[j].v,w=e[j].w;
if(d[v]>book[u]+w){ //每一轮只更新边长为1的周边点
d[v]=book[u]+w;
flag=false;
}
}
if(flag) break; //若没有进行过松弛,则提前跳出
}
}
判断是否有负环存在
只需要将松弛轮数从n-1改成n,看看新加的一轮是否还会有松弛现象发生,若有,则存在负环。
int n,m,k,d[N];
struct Edge{
int u,v,w;
}e[maxn];
bool Bellman_ford(int s){
for(int i=1;i<=m;i++) cin>>e[i].u>>e[i].v>>e[i].w;
memset(d,INF,sizeof(d));
d[s]=0;
bool flag; //判断每一轮是否进行了松弛
for(int i=1;i<=n;i++){
flag=true;
for(int j=1;j<=m;j++){
int u=e[j].u,v=e[j].v,w=e[j].w;
if(d[u]!=INF&&d[v]>d[u]+w){ //d[u]!=INF的条件在所有的权值都不含负数的情况下不需要
d[v]=d[u]+w;
flag=false;
}
}
if(flag) break; //若没有进行过松弛,则提前跳出
}
if(!flag) return true; //若第n轮还能进行松弛,则说明有负环
return false;
}
SPFA算法----队列优化的Bellman-ford算法
平均时间复杂度是O(m);最坏时间复杂度是O(nm)
可以处理负权和负权边,也可以判断负权回路。还可以处理有边数限制的最短路问题。
求单源最短路
vector<PII>g[maxn]; //用邻接表存边和权值
bool vis[maxn]; //vis[i]判断点i是否在队列里,若在则是true
int d[maxn];
void spfa(int s){
memset(d,INF,sizeof(d));
memset(vis,false,sizeof(vis));
queue<int>q;
d[s]=0; vis[s]=true;
q.push(s);
while(!q.empty()){
int u=q.front(); q.pop();
vis[u]=false; //弹出以后点u不在队列里
for(auto it:g[u]){
int v=it.first,w=it.second;
if(d[v]>d[u]+w){
d[v]=d[u]+w;
if(!vis[v]){
vis[v]=true; q.push(v);
}
}
}
}
}
判断整个图中是否有负环存在
判断负环需要注意的两点
- 初始时所有点都需要入队,以遍历到所有可能含负环的起点
- 通过一个cnt[i]数组用来维护从最短路起点到点i的边条数,若大于等于n则说明至少有某个点走过两次,即含有负环。
vector<PII>g[maxn]; //用邻接表存边和权值
bool vis[maxn]; //vis[i]判断点i是否在队列里,若在则是true
int d[maxn],cnt[maxn];
bool spfa(){
queue<int>q;
for(int i=1;i<=n;i++){
q.push(i); cnt[i]=0; vis[i]=true; d[i]=INF;
}
while(!q.empty()){
int u=q.front(); q.pop();
vis[u]=false;
for(auto it:g[u]){
int v=it.first,w=it.second;
if(d[v]>d[u]+w){
d[v]=d[u]+w;
cnt[v]=cnt[u]+1;
if(cnt[v]>=n) return true;
if(!vis[v]){
vis[v]=true; q.push(v);
}
}
}
}
return false;
}
最近公共祖先(LCA)倍增做法-查询效率O(logn)
int depth[maxn],fa[maxn][30];
vector<int>g[maxn];
void dfs(int u,int f,int d){
fa[u][0]=f; depth[u]=d;
int len=g[u].size();
for(int i=0;i<len;i++){
int v=g[u][i];
if(v!=f) dfs(v,u,d+1);
}
}
void Init(){
dfs(s,-1,1);
for(int j=0;(1<<j+1)<=n;j++){
for(int i=1;i<=n;i++){
if(fa[i][j]==-1) fa[i][j+1]=fa[i][j];
else fa[i][j+1]=fa[fa[i][j]][j];
}
}
}
int LCA(int u,int v){
if(depth[u]<depth[v]) swap(u,v);
int temp=depth[u]-depth[v];
for(int i=0;(1<<i)<=temp;i++){
if(temp&(1<<i)) u=fa[u][i];
}
if(u==v) return u;
for(int i=log2(n);i>=0;i--){
if(fa[u][i]!=fa[v][i]){
u=fa[u][i]; v=fa[v][i];
}
}
return fa[u][0];
}
树上差分
树上差分分为点权差分和边权差分。
作用:在O(1)的复杂度修改树链权值的前提下,统计某一特定长度的树链的权值和。
#include <bits/stdc++.h>
#include <random>
using namespace std;
const int maxn=2e6+10;
int n,ans[maxn];
int fa[maxn][30];
vector<int>g[maxn];
void dfs(int u,int f){
fa[u][0]=f;
int len=g[u].size();
for(int i=0;i<len;i++){
int v=g[u][i];
if(v!=f) dfs(v,u);
}
}
void Init(){
for(int j=0;(1<<j+1)<n;j++){
for(int i=1;i<=n;i++){
if(fa[i][j]==-1) fa[i][j+1]=-1;
else fa[i][j+1]=fa[fa[i][j]][j];
}
}
}
int ask(int x,int k){
for(int i=24;i>=0;i--){
if((k>>i)&1) x=fa[x][i];
}
return max(x,0);
}
//对差分数组求和,从叶子往根求权值和
void dfs_ans(int u,int fa){
for(auto it:g[u]){
if(it!=fa){
dfs_ans(it,u);
ans[u]+=ans[it];
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
cin>>n;
memset(ans,0,sizeof(ans));
rep(i,1,n-1){
int u,v; cin>>u>>v;
g[u].eb(v); g[v].eb(u);
}
dfs(1,-1); Init();
rep(i,1,n) {
int x; cin>>x;
ans[i]++;
ans[ask(i,x+1)]--;
}
dfs_ans(1,-1);
rep(i,1,n) cout<<ans[i]<<" ";
return 0;
}
动态规划方面
背包问题
01背包O(n*k)
f[0][0]=0;
for(int i=1;i<=n;i++){
for(int j=0;j<=k;j++){
f[i][j]=f[i-1][j];
if(j>=v[i]) f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
}
}
01背包+滚动数组优化O(n*k)
f[0]=0;
for(int i=1;i<=n;i++){
for(int j=k;j>=0;j--){
if(j>=v[i]) f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
完全背包O(n*k)
f[0][0]=0;
for(int i=1;i<=n;i++){
for(int j=0;j<=k;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]);
}
}
完全背包+滚动数组优化O(n*k)
f[0]=0;
for(int i=1;i<=n;i++){
for(int j=0;j<=k;j++){
if(j>=v[i]) f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
多重背包O(nms)
f[0][0]=0;
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
f[i][j]=f[i-1][j];
for(int k=0;k<=s[i]&&k*v[i]<=j;k++){
f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
}
}
}
多重背包+滚动数组优化O(nms)
f[0]=0;
for(int i=1;i<=n;i++){
for(int j=m;j>=0;j--){
for(int k=0;k<=s[i]&&k*v[i]<=j;k++){
f[j]=max(f[j],f[j-k*v[i]]+k*w[i]);
}
}
}
线性dp
最长上升子序列(LIS)
暴力做法 时间复杂度O(n*n)
int d[maxn],n;
int LIS(int a[]){
for(int i=1;i<=n;i++) d[i]=1;
int res=0;
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
if(a[i]>a[j]) d[i]=max(d[i],d[j]+1);
}
res=max(res,d[i]);
}
return res;
}
贪心+二分优化 时间复杂度O(nlogn)
int low[maxn],res,n;
int LIS(int a[]){
memset(low,INF,sizeof(low));
low[1]=a[1]; res=1;
for(int i=2;i<=n;i++){
if(a[i]>low[res]) low[++res]=a[i];
else{
int pos=lower_bound(low+1,low+1+res,a[i])-low; //最长递增子序列
int pos=upper_bound(low+1,low+1+res,a[i])-low; //最长非递减子序列
low[pos]=a[i];
}
}
return res;
}
最长公共子序列 (LCS)
暴力 时间复杂度O(m*n)
int f[maxn][maxn],n,m;
int LCS(int a[],int b[]){
memset(f,0,sizeof(f));
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(a[i]==b[j]) f[i][j]=f[i-1][j-1]+1;
else f[i][j]=max(f[i-1][j],f[i][j-1]);
}
}
return f[n][m];
}
借助LIS优化 时间复杂度O(nlogn)
int low[maxn],res,n,m;
int a[maxn],b[maxn];
int LIS(int a[]){
memset(low,INF,sizeof(low));
low[1]=a[1]; res=1;
for(int i=2;i<=n;i++){
if(a[i]>low[res]) low[++res]=a[i];
else{
int pos=lower_bound(low+1,low+1+res,a[i])-low;
low[pos]=a[i];
}
}
return res;
}
int LCS(int a[],int b[]){
unordered_map<int,int>mp;
//n为序列a的长度,m为序列b的长度
//我们把序列b当成是递增的对照组,再将序列a映射成一个对应的序列,切记映射需初始化为-1
//这样当序列a中的元素未存在序列b时,映射结果便为-1,不会计入LIS中
for(int i=1;i<=n;i++) mp[a[i]]=-1;
for(int i=1;i<=m;i++) mp[b[i]]=i;
for(int i=1;i<=n;i++) a[i]=mp[a[i]];
return LIS(a);
}
区间dp
状压dp
树上dp
概率dp(期望dp)
树状数组
每一次操作的时间复杂度为O(logn)
数组c[x]储存的是区间[x,x-lowbit(x)+1]的值。
单点更新,查询区间求和
int c[maxn];
inline int lowbit(int x) { return x&(-x); }
void update(int x,int y){ //节点x上加上y
while(x<=n){
c[x]+=y;
x+=lowbit(x);
}
}
int getsum(int x){ //求区间[1,x]的和
int res=0;
while(x>0){
res+=c[x];
x-=lowbit(x);
}
return res;
}
区间更新,查询单点
lowbit、updata、getsum三个函数完全与上一致。
唯一的不同在于建树使用的值不是原值a[i],而是序列a的差分值---->差分建树
int c[maxn],a[maxn],n,q;
inline int lowbit(int x) { return x&(-x); }
void update(int x,int y){
while(x<=n){
c[x]+=y;
x+=lowbit(x);
}
}
int getsum(int x){
int res=0;
while(x>0){
res+=c[x];
x-=lowbit(x);
}
return res;
}
int main() {
cin>>n>>q;
rep(i,1,n){
cin>>a[i];
if(i==1) update(i,a[i]);
else update(i,a[i]-a[i-1]);
}
while(q--){
int opt,l,r,k; cin>>opt;
if(opt==1) { //区间更新
cin>>l>>r>>k;
update(l,k);
update(r+1,-k);
}
else {
cin>>l; //单点查询
cout<<getsum(l)<<endl;
}
}
求逆序对O(nlogn)
大部分情况需要离散化
情况一:逆序对定义:i<j但a[i]>a[j]
int c[maxn],a[maxn],lsh[maxn],n,cnt;
LL ans;
inline int lowbit(int x) { return x&(-x); }
void update(int x){
while(x<=n){
c[x]++;
x+=lowbit(x);
}
}
int getsum(int x){
int res=0;
while(x>0){
res+=c[x];
x-=lowbit(x);
}
return res;
}
//获得离散化后的排序值
inline int get(int x) { return lower_bound(lsh+1,lsh+1+cnt,x)-lsh; }
int main() {
for(int i=1;i<=n;i++){
cin>>a[i];
lsh[i]=a[i];
}
//将数组a进行离散化
sort(lsh+1,lsh+1+n);
cnt=unique(lsh+1,lsh+1+n)-lsh-1;
for(int i=1;i<=n;i++){
int x=get(a[i]);
update(x);
ans+=(i-getsum(x));
}
情况二:逆序对定义:i<j但a[i]>=a[j]
函数lowbit、update和getsum与上面完全一致。
唯一的不同在于由于a[i]和a[j]可以相等,所以我们的个数应该是首先减去本身,然后减去排序值比本身小一的个数,即 i-1-getsum(x-1)。
int c[maxn],a[maxn],lsh[maxn],n,cnt;
LL ans;
inline int lowbit(int x) { return x&(-x); }
void update(int x){
while(x<=n){
c[x]++;
x+=lowbit(x);
}
}
int getsum(int x){
int res=0;
while(x>0){
res+=c[x];
x-=lowbit(x);
}
return res;
}
//获得离散化后的排序值
inline int get(int x) { return lower_bound(lsh+1,lsh+1+cnt,x)-lsh; }
int main() {
for(int i=1;i<=n;i++){
cin>>a[i];
lsh[i]=a[i];
}
//将数组a进行离散化
sort(lsh+1,lsh+1+n);
cnt=unique(lsh+1,lsh+1+n)-lsh-1;
for(int i=1;i<=n;i++){
int x=get(a[i]);
update(x);
ans+=(i-1-getsum(x-1));
}
求区间最值
每一次查询或更新的操作的时间复杂度都为O(logn*logn)
int n,a[maxn],c[maxn];
inline int lowbit(int x)
{
return x&(-x);
}
void update(int x){
c[x]=a[x];
for(int i=1;i<lowbit(x);i<<=1){
c[x]=max(c[x],c[x-i]);
}
}
int query(int l,int r){
int res=a[r];
while(l<=r){
res=max(a[r],res);
for(--r;r-l>=lowbit(r);r-=lowbit(r)){
res=max(res,c[r]);
}
}
return res;
}
ST表
维护区间最值的离线算法。
- 不支持在线修改
- 预处理的算法复杂度为O(nlogn)
- 每次查询区间最值的时间复杂度为O(1)
int st[32][maxn]; //st[i][j]表示区间[j,j+2^i-1]的最大值,区间长度为2^i
void Init(){
for(int i=1;i<=n;i++) st[0][i]=read();
for(int i=1;i<=30;i++){
for(int j=1;j+(1<<i)-1<=n;j++){
st[i][j]=max(st[i-1][j],st[i-1][j+(1<<i-1)]);
}
}
}
int query(int l,int r)
{
int k=log2(r-l+1);
return max(st[k][l],st[k][r-(1<<k)+1]);
}