csp复试备考——BY Xzhhng

本文是一份CSP复赛备考的知识点总结,涵盖了位运算、前缀与差分、排序、二分查找、倍增算法、栈、队列、优先队列、链表、哈希、字符串算法、图论、质数和约数计算、矩阵乘法、组合数学等多个数据结构和算法主题,并提供了相关代码示例。适合参赛者复习和准备。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

各种知识点的知识和板子复习

位运算的基础知识

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[l1]

差分

给定一个序列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
### 关于CSP考试中的STL复习 #### STL容器的选择与应用 标准模板库(STL)提供了多种容器,每一种都有其特定的应用场景。对于频繁插入删除操作的数据序列推荐使用`list`或者`deque`;如果数据量较大且需要快速随机访问,则应考虑`vector`或`array`[^1]。 ```cpp std::vector<int> vec; // 动态数组, 支持高效的随机存取 std::list<int> lst; // 双向链表, 插入删除效率高 ``` #### 常见算法实现技巧 利用<algorithm>头文件下的函数可以简化很多编程任务。例如,在处理重复局面检测这类问题时,可以直接调用`equal()`来比较两个范围内的元素是否相等,从而判断是否存在相同的棋盘状态。 ```cpp bool isSameBoard(const char boardA[8][8], const char boardB[8][8]) { return std::equal(&boardA[0][0], &boardA[0][0]+64, &boardB[0][0]); } ``` #### 容器适配器的理解 除了基本容器外,还有一些基于这些基础构建的特殊用途容器——即所谓的“容器适配器”。比如栈(stack),队列(queue),优先级队列(priority_queue)[^1]。它们通过限制某些成员函数接口的方式实现了更专业的功能需求。 ```cpp std::stack<int> stk; stk.push(1); stk.pop(); ``` #### 自定义迭代器设计 当面对复杂结构体如树形结构或是图论模型时,自定义迭代器能够极大地方便遍历过程并提高代码可读性和维护性。这通常涉及到重载运算符以及遵循一定的协议规范。 ```cpp class TreeIterator : public std::iterator<std::input_iterator_tag, TreeNode*> { public: bool operator!=(const TreeIterator& other) { /* ... */ } value_type& operator*() { /* ... */ } }; ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值