各种知识点的知识和板子复习
位运算的基础知识
1<<i->2^i
x=x<<(1<<i)->将x的i+1位强行变为1
if(x&(1<<i))->判断第i位是否为1
x>>1->将x的最后一位删掉
for(int i=S;i;i=(i-1)&S)->枚举S的子集
lowbit(x)---x&(-x)->取x的最后一位
b&1->判断b是否是奇数
前缀与差分
前缀
给定一个序列A,它的前缀和S的信息就是:
S[i]= ∑ i = l r A [ i ] = S [ r ] − S [ l − 1 ] \sum_{i=l}^{r}{A[i]}=S[r]-S[l-1] ∑i=lrA[i]=S[r]−S[l−1]
差分
给定一个序列A,它的前缀和S的信息就是:
S[1]=A[1] S[i]=A[i]-A[i-1] (2<=i<=n)
差分和前缀和的应用
前缀和可以在O(1)的时间求出一段区间的和,但是无法修改
差分可以将一个区间操作转换成一个单点修改,但是无法O(1)求出一个数的值
前缀和和差分是成互逆的,所以一个序列前缀了一遍再差分还是原序列
排序
sort
二分
二分查找
对于一个具有单调性的序列,一般的我们都可以直接用STL
对于一个序列上的二分,一般用如下的两种:
lower_bound(a+1,a+n+1,now)-a
a 数组,返回now值的下标 ,查找>=now的第一个数
upper_bound(a+1,a+n+1,now)-a
数组,返回now值的下标,查找>now的第一个数
lower_bound(a.begin(),a.end(),now)-a.begin() 对于vector写法
s.lower_bound(x) set写法
二分答案
一般就是如下的模板
int l=0,r=n;
while(l<r)
{
int mid=l+r>>1;
if(check(mid))r=mid;
else l=mid+1;
}
倍增
RMQ
通过倍增O(log(n))的预处理,可以O(1)回答一个区间里的最大值或最小值(但是不能修改),代码如下
//先O(nlogn的预处理)处理出发f[i][j]表示i到2^i这一段区间的最大值
//建议不要用log,这个常数巨大
for(int j=1;j<=20;j++)
for(int i=1;i<=n-(1<<j)+1;i++)
f[i][j]=min(f[i][j-1],f[i+(1<<j-1)][j-1]);
for(int i=1;i<=m;i++)
{
int x,y;scanf("%d%d",&x,&y);
int k=log2(y-x+1);
printf("%d ",min(f[x][k],f[y-(1<<k)+1][k]));
}
关于之后的倍增后面说
栈
c++的内置栈的用法
stack
定义
stack< int >sta;
stack< char >sta;
stack< pair<int,int> >sta;
stack< 结构体 >sta;
常见用法
sta.push(i)->插入操作
sta.pop()->弹出栈顶元素
sta.empty()->判断栈是否为空,如果为空返回1,不是返回0
sta.size()->查找目前栈的元素
sta.top()->访问栈顶元素
队列
queue
定义
queue< int >q;
queue< char >q;
queue< pair<int,int> >q;
queue< 结构体 >q;
常见用法
q.push(i)->队头入队操作
q.pop()->弹出队列头部元素
q.empty()->判断栈是否为空,如果为空返回1,不是返回0
q.size()->查找目前的队里元素个数
q.front()->访问队头元素
duque(双端队列)
定义
deque< int >q;
deque< char >q;
deque< pair<int,int> >q;
deque< 结构体 >q;
常见用法
q[]->随机访问
q.begin()->头指针
q.end()->尾指针
q.front()->队头元素
q.back()->队尾元素
q.push_front(i)->队头入队操作
q.push_back(i)->队尾入队操作
q.pop_front()->弹出队头元素
q.pop_back()->弹出队尾元素
q.empty()->判断队列是否为空,如果为空返回1,不是返回0
q.size()->查找目前的个数
q.clear()->清空队列(O(n))
priority_queue(优先队列,堆)
定义
priority_queue< int >q;
priority_queue< char >q;
priority_queue< pair<int,int> >q;
priority_queue< 结构体 >q;
常见用法
q.push(i)->队头入队操作
q.pop()->弹出队列头部元素
q.empty()->判断栈是否为空,如果为空返回1,不是返回0
q.size()->查找目前的队里元素个数
q.top()->访问队头元素
链表与邻接表
链表
一个支持O(1)的插入和删除但是并不能O(1)随机查询
下面是链表的数组写法
struct cow{
int value;//插入的值
int prv;//前驱
int nxt;//后继
}a[SIZE];//定义
int head,tail,tot;
//新建链表
void initialize()
{
tot=2;head=1,tail=2;
a[head].nxt=tail;
a[tail].prv=head;
}
//在p的后面插入一个值为val的元素
void insert(int p,int val)
{
q=++tot;
a[q].value=val;
a[a[p].nxt].prv=q;
a[q].nxt=a[p].nxt;
a[p].nxt=q;
a[q].prv=p;
}
//删除p
void remove(int p)
{
a[a[p].prv].nxt=a[p].nxt;
a[a[p].nxt].prv=a[p].pre;
}
//清空
void clear_()
{
head=tot=tail=0;
memset(a,0,sizeof(a));
}
Hash
这个东西一般可以用map,这里贴的板子,一般用于对于数组或字符串的Hash,这里贴个代码
#define Mod 100007
struct cow{
bool z;string s;
}H[Mod];
int Hash(string s)
{
int len=s.size(),Ha=0;
for(int i=0;i<len;i++)
Ha=(Ha+s[i]*(i+1))%Mod;
while(s!=H[Ha].s&&H[Ha].z)Ha=(Ha+Mod)%Mod;
H[Ha].s=s;H[Ha].z=1;
return Ha;
}
字符串
KMP
next[1]=0;
for(int i=2,j=0;i<=n;i++){
while(j>0&&a[i]!=a[j+1])j=next[j];
if(a[i]=a[j+1])j++;
next[i]=j;
}//先自我匹配
for(int i=1,j=0;i<-m;i++){
while(j>0&&(j==n||b[i]!=a[j+1]))j=next[j];
if(b[i]==a[j+1]j++;
f[i]=j;
if(f[i]==n)//此时就是A在B中某一次出现
}
Manachar
scanf("%s",a);
string s;
int len=strlen(len);
int k=0;s[k++]='$';
s[++k]='#';
for(int i=0;i<l;i++)
{
s[k++]=a[i];
s[k++]='#';
}
s[k]='\0';
//s的字串的形式$#A#B#D#F#Y#S#D#\0
//好处,这样就不用分别处理奇数回文串和偶数回文串的情况
Manachar();int p[SIZE];
//p[i]表示以i为中心的最大回文串的长度
void Manachar()
{
int mx,id;
for(int i=0;s[i]!='\0';i++)
{
if(mx>i)//满足当前的i在边界范围内,说明可以直接附上初值
p[i]=min(p[2*id-i],mx-i);//找到当前I关于id的对称点的p[]值,并在与mx-i取最小值
else p[i]=1;//自己形成一个回文串
while(b[i+p[i]]==b[i-p[i]])p[i]++;//查找是否还有符合的
if(p[i]+i>mx)//超找到了目前最大的范围
{
mx=p[i]+i;id=i;
}
}
}
Trie
这里就贴模板
int trie[SIZE][26],tot,end[N];
void insert(char* str)//插入一个字符串
{
int len=strlen(str);//注意这个是O(n)的,不要放在for循环的判断语句里
for(int k=0;k<len;k++){
char c=str[k]-'a';
if(trie[p][ch]==0)trie[p][ch]=++tot;
p=trie[p][ch];
}
end[p]=1;//记录最后的位置
}
bool search(char* str)//查找这个字符串存不存在
{
int len=strlen(str),p=1;
for(int k=0;k<len;k++)
{
p=trie[p][str[k]-'a'];
if(p==0)return 0;
}
return end[p];
}
图的遍历
这里贴个板子
int v[N];
void dfs(int x)
{
v[x]=1;
for(int i=head[x];i;i=e[i].x)
{
int y=e[i].y;
if(v[y])continue;
...
dfs(y);
...
}
}
拓扑排序
概念:在有向图上,如果有一条x->y的边,那么拓扑排序后的x一定在y之前
for(int i=1;i<=m;i++)
{
int x,y;scanf("%d%d",&x,&y);
inse(x,y);in[y]++;
}
tuopusort();
queue<int>q;int tot,a[N];
void toppusort()
{
q.clear();tot=0;
for(int i=1;i<=n;i++)
if(!in[i])q.push(i);
while(!q.empty())
{
int x=q.front();q.pop();a[++tot]=x;
for(int i=head[x];i;i=e[i].x)
{
int y=e[i].y;in[y]--;
if(!in[y])q.push(y);
}
}
}
//a即为拓扑后的序列
if(tot<n)//判断是否有环
用途:一般用于判断一个有向图是否有环和DP排除后效性的方法
DFS和BFS
两个都是暴力,板子就不贴了
迭代加深
是一个有dfs和bfs的优点的一个算法(既不用储存状态,又可以优先求最小值的一个算法),代码模板如下:
int deep;//全局变量
bool dfs(int x){
if(x>deep)return 0;
...
...
return 1;
}
while(dfs(1))deep++;
cout<<deep;
双端BFS
这个没什么好说的,就是用两个队列来维护BFS,当两个都走到一个点时,就可以输出答案,主要是用于减小搜索树的大小
A*
这是一个带估价函数的BFS,我们可以用估价函数来来减小缩小的范围,但是我们一定要保证估价函数的正确性,也就是我们设计出来估价函数一定不能大于我们答案,当我梦设计出来的估价函数越接近答案时,我们的dfs搜索范围就会减小,当等于答案时就是一个很优的搜索了(或者已经不叫搜索了,因为这已经是线性复杂度了)当我们设计不出来一个完美的估价函数时我们可以用两个不怎么优的估价函数取一个Max这样即可减小搜索范围
IDA*
这个和 A ∗ A^* A∗差不多,是一个基于迭代加深的估价函数,也差不多
质数
筛法求素数(我觉得O(nloglogn)已经很好了)
int p[N];//p[i]表示i是不是质数
void prime()
{
for(int i=2;i<=N;i++)
{
if(p[i])continue;
for(int j=2*i;j<=N;j+=i)
p[j]=1;
}
}
质因数分解
试除法,时间复杂度O( n \sqrt n n)
int p[N],c[N],tot;
for(int i=2;i<=sqrt(n);i++)
{
if(n%i!=0)continue;
p[++tot]=i;c[tot]=0;
while(n%i==0)n/=i,c[tot]++;
}
if(n>1)p[++m]=n,c[m]=1;
约数
试除法 O( n \sqrt n n) 求一个数的所有约数
int c[N],tot;
for(int i=1;i*i<=n;i++){
if(n%i==0){
c[++tot]=i;
if(i!=n/i)c[++tot]=n/i;
}
}
//注意:不一定有序,可以O(n)类似归并的方法合并
倍数法 O( n l o g n nlogn nlogn)求出所有的数的个数
vector<int>c[N];
for(int i=1;i<=n;i++)
for(int j=1;j<=n/i;j++)
c[i*j].push_back(i);
//c[i]即为i的约数
最大公约数 O( n l o g n nlogn nlogn)
int gcd(int x,int y){
return y?x:gcd(b,a%b);}
欧拉函数
定义:1~n中与n互质的个数,记为 φ ( n ) \varphi(n) φ(n),由于它的性质,我们只要求出它的质因数分解,顺便求出 φ ( n ) \varphi(n) φ(n)即可
试除法 O( n \sqrt n n)求出一个数的欧拉函数
int phi(int n)
{
int ans