蓝桥杯竞赛向(C/C++语言)之字符串算法篇


前言

本次博客先会输出DFS的一些题目,DFS蓝桥考的真的不少,掌握需要大量的刷题,这点题目是完全不够的,有时间的话可以去网站刷,之后是字符串的内容

黄金树

黄金树
DFS 和 树结合也是常考题目,每年基本上会有关于树问题的题目
这个题确实不难,对DFS熟悉的可以轻松码出来

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define inf 0x3f3f3f3f
#define endl '\n'
#define int long long 
#define ld long double
#define vt vector<int> 
const int mod = 1e9 + 7;
const int N = 1e5 + 7;
int n;
struct node{
  int val,left,right;//儿子
}w[N];
int ans;
void dfs(int x,int go)
{
    if(x > n) return;
    if(go == 0) ans += w[x].val;
    int ls = w[x].left;
    int rs = w[x].right;
    if(ls != -1) dfs(ls,go + 1);
    if(rs != -1) dfs(rs,go - 1);
    if(ls == -1 && rs == -1) return;
}
void solve()
{
  cin >> n;
  for(int i = 1;i <= n;i++) cin >> w[i].val;
 for(int i = 1;i <= n;i++) cin >> w[i].left >> w[i].right;
 dfs(1,0);
 cout << ans;
}
signed main()
{
 ios::sync_with_stdio(false);
 cin.tie(0),cout.tie(0);
 int _ = 1; 
 //cin >> _;
 while(_ --) solve();
    return 0;
}

混境之地5
还是跑图题,注意要加记忆化搜索,否则会T
dp数组的意义就是dp[i][j][k]表示用了k次(0/1)次是否能到i,j这个位置

#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 1;
int mp[N][N];
int n,m,k;
int x1,x2,fy,y2; 
int dx[] = {0,0,1,-1};
int dy[] = {1,-1,0,0};
int dp[N][N][2];
bool h(int x,int y)
{
  return x >= 1 && x <= n && y >= 1 && y <= m;
}
bool dfs(int x,int y,int t)
{
  if(x == x2 && y == y2) return true;
  if(dp[x][y][t] != -1) return dp[x][y][t];
  for(int i = 0; i <= 3;i ++)
  {
    int nx = x + dx[i],ny = y + dy[i];
    if(!h(nx,ny)) continue;
    if(!t)
    {
      //用
    if(mp[x][y] + k >= mp[nx][ny] && dfs(nx,ny,1)) return dp[x][y][t] = true; 
    //不用
    if(mp[x][y] >= mp[nx][ny] && dfs(nx,ny,0))  return dp[x][y][t] = true;
    }
    else
    {
       if(mp[x][y] >= mp[nx][ny] && dfs(nx,ny,1)) return dp[x][y][t] = true; 
    }
  }
  return dp[x][y][t] = false;
}
int main()
{
  memset(dp,-1,sizeof(dp));
  ios::sync_with_stdio(false);
  cin.tie(0),cout.tie(0);
   cin >> n >> m >> k;
 cin >> x1 >> fy >> x2 >> y2;

  for(int i = 1; i <= n ; i++)
  {
    for(int j = 1; j <= m; j++) cin >> mp[i][j];
  }
  cout << (dfs(x1,fy,0) ? "Yes" :" No")<< '\n';
    
  
  return 0;
} 

混境之地2
这个题和上面那个很像,但是有区别,这个题行走只要不走到墙可以无限转圈,所以要加个vis数组,记得要回溯,不然包错的

#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 1;
char mp[N][N];
int n,m,k;
int x1,x2,yf,y2; 
int dx[] = {0,0,1,-1};
int dy[] = {1,-1,0,0};
int dp[N][N][2];
int vis[N][N];
bool h(int x,int y)
{
  return x >= 1 && x <= n && y >= 1 && y <= m;
}
bool dfs(int x,int y,int t)
{
  if(x == x2 && y == y2) return true;
  if(dp[x][y][t] != -1) return dp[x][y][t];
  for(int i = 0; i <= 3;i ++)
  {
    int nx = x + dx[i],ny = y + dy[i];
    if(vis[nx][ny] == 1) continue;
    vis[nx][ny] = 1;
    if(!h(nx,ny)) continue;
    if(!t)
    {
      //用
    if(mp[nx][ny] == '#'&& dfs(nx,ny,1)) return dp[x][y][t] = true; 
    //不用
    if(mp[nx][ny] == '.' && dfs(nx,ny,0))  return dp[x][y][t] = true;
    }
    else
    {
      if(mp[nx][ny] == '.' && dfs(nx,ny,1)) return dp[x][y][t] = true; 
    }
    vis[nx][ny] = -1;//要注意回溯
  }
  return dp[x][y][t] = false;
}
int main()
{
  memset(dp,-1,sizeof(dp));
   memset(vis,-1,sizeof(vis));
  ios::sync_with_stdio(false);
  cin.tie(0),cout.tie(0);
   cin >> n >> m;
  cin >> x1 >> yf >> x2 >> y2;
  for(int i = 1; i <= n ; i++)
  {
    for(int j = 1; j <= m; j++) cin >> mp[i][j];
  }
  cout << (dfs(x1,yf,0) ? "Yes" :" No")<< '\n';
  return 0;
} 

字符串

本次讲的算法有kmp等(自己看目录),ac自动机会给个链接,还有一些例题。。。要吐了

KMP

KMP是一种字符串匹配算法,看到匹配或者一些玩前后缀的时候可以想这个,核心是处理nex数组(不要起名为next,会和库里的重复。。。。。)比如字符串p 在 s中出现的次数,朴素会到O(n * m)级别,KMP只需O(n + m)级别,不用研究nex数组怎么来的,会用就行
nex[i]表示在i这个位置,最长真相同前后缀的长度,也表示匹配失败时应该滚去的位置
比如 abaacd 的nex 就是0 0 1 1 0 0
模板

//n为字符串长度
注意:char s[100];cin >> s + 1;
  int n = strlen(s + 1);//同步
nex[0] = nex[1] = 0;
for (int i = 2, j = 0; i <= n; i++)
{
    while (j && p[i] != p[j + 1]) j = nex[j];
    if (p[i] == p[j + 1]) j++;
    nex[i] = j;
}    

自己匹配自己的模板

#include <bits/stdc++.h>
//标准 KMP算法
//在 s中查找p出现的次数
using namespace std;
const int N = 1e6 + 1;
char s[N], p[N];
int nex[N], ans;
int main()
{
   // cin >> p + 1 >> s + 1;
    int n = strlen(p + 1);
    int m = strlen(s + 1);
    //进行处理nex数组
    nex[0] = nex[1] = 0;
    for (int i = 2, j = 0; i <= n; i++)
    {
        while (j && p[i] != p[j + 1]) j = nex[j];
        if (p[i] == p[j + 1]) j++;
        nex[i] = j;
    }  
    //    //对于样例abcabc  //  nex 为  0 0 0 1 2 3
    for (int i = 1, j = 0; i <= m; i++)
    {
        while (j && s[i] != p[j + 1]) j = nex[j];
        if (s[i] == p[j + 1]) j++;
        if (j == n) ans++;
    }
    cout << ans;
    return 0;
}

例题后面一起放,可能字数会很多()

Manacher

最开始听别人说的一直以为是马拉车()原来还真是谐音梗
马拉车算法时有关回文字符串的算法,看到回文可以往这个想
先介绍一个概念为了后面的铺垫,
回文半径:比如字符串aabaa,对于回文中心b,回文半径就是三,但是对于偶数长度的字符串,很尴尬;
所以manacher算法的第一个处理,就是将字符串处理成奇数长度的字符串,把首尾和字符串中间加上特殊字符,比如原来的字符串是abba,处理完之后就变成^#a#b#b#a#@,注意首尾不同,其余都用#处理
对于当前的某一个位置i的回文半径p[i],说明原来回文串长度是p[i] - 1,很好理解和证明,记住就行。
!重要的是处理
模板,和KMP一样记住就行

 int n = strlen(s + 1);
  //填充  倒着填防止被覆盖,除非额外开一个数组!
  s[0] = '@',s[2 * n + 2] = '$';
  for(int i = 2 * n + 1; i >= 1;i--)
  s[i] = i & 1 ? '#' : s[i >> 1];//写成if判断一个样
   //或者这样
  ns[0] = '@',ns[2 * n + 2] = '$';
  for(int i = 1;i <= 2 * n + 1;i++)
  {
    ns[i] = i & 1 ?'#':s[i >> 1];
  }
  

  //处理
  int c = 0, R = 0; // r是当前最右回文边界,c是中心
  for(int i = 1;i <= 2 * n + 1;i ++)
  {
    int mirror = 2 * c - i;
    p[i] = i < R ? min(R - i, p[mirror]) : 1;
    while(s[i + p[i]] == s[i - p[i]]) p[i]++;
    if(i + p[i] > R) c = i, R = i + p[i];
  }

例题依旧放在后面

字符串哈希

这个感觉是很实用的算法,用数字表示字符串,降低处理字符串的复杂度,比如在s中寻找p的出现次数就可以适用字符串哈希,将每一段的值以O(1)算出
记得开unsigned long long,如果开long long可能会导致哈希冲突,还得设两个标准值,标准值相当于进制,一般是一个质数,比如131等,类比十进制
模板—背下来

//字符串哈希
#include<bits/stdc++.h>
#define ull unsigned long long
const ull base = 131;//base一般是一个质数
const int N = 1e6 + 1;
ull h[N],b[N]; //h[i] 表示s[1-i]的hash ---前缀和
char s[N];
ull getHash(int l,int r)
{
   return h[r] - h[l - 1] * b[r - l + 1];
}
using namespace std;
int main()
{
cin >> s + 1;
int n;
n = strlen(s + 1);
b[0] = 1;
for(int i = 1;i <= n;i++) h[i] = h[i - 1] * base + (int)s[i];//这里直接转成int就行,懒得去减
for(int i = 1;i <= n;i++) b[i] = b[i - 1] * base;//获取base的i次方
//获取子串l - r 的hash
getHash()
  return 0;
}

字典树

给N个字符串S1 到 Sn,查找t是否在这里出现过,朴素的查法就是遍历,比较是否相等,复杂度较高,下面引入更快的方法,字典树,叫做字典树,就是一颗树形结构,每一条边代表一个字符,每一个结点可以存储可以不存储,看具体的题干
模板:
(字典树用的不多,更多的是下面的01trie,主要放在后面重点的讲解)

//下面是利用字典树查找的代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define inf 0x3f3f3f3f
#define endl '\n'
const int N = 1e6 + 1ll;
int nex[N][27]; //nex[i][0]表示从节点i出发,边为a的下一个节点地址
int cnt[N];//cnt[i]表示以节点i为结尾的字符串数量
int idx = 2;//记录结点
void insert(char s[])
{
    int n = strlen(s + 1);
    int x = 1; //从1开始往下走
    //将每一个字符取出操作
for(int i = 1;i <= n;i++)
{
   //不存在这一条边就去开一个边
    if(!nex[x][s[i] - 'a']) nex[x][s[i]-'a'] = idx++;
    //往下走
    x = nex[x][s[i] - 'a'];
}
   cnt[x]++;
}
int check(char s[])
{
    int n = strlen(s + 1);
    int x = 1;
    for(int i = 1;i <= n; i++)
    {
        if(!x) return 0;
        x = nex[x][s[i]-'a'];
    }
    return cnt[x];
}
int main()
{
 ios::sync_with_stdio(false);
 cin.tie(0),cout.tie(0);
    return 0;
}


对nex数组不理解的来看这个图奥,nex[8][‘c’ - ‘a’] = 9;nex[1][‘a’ - ‘a’] = 2;这样基本就能看懂了

01trie树

是一种二叉树 每个叶子节点对应一个二进制数
解决最大异或对问题
01Trie树支持三种操作
插入
查询与x异或的最大的元素
查询比更大的元素的个数,可用于解决逆序对问题
有了字典树的模板,更好写这个了

///字典树
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define inf 0x3f3f3f3f
#define endl '\n'
#define int long long 
const int N = 1e6 + 1; //一般要开已有数据的30倍数
int son[N][2];
int e[N];
int iot = 1;
void insert(int x)
{
    int o = 1;//表示节点编号
    e[o]++;
    for(int i = 30;i >= 0;--i)
    {
        int y = x >> i & 1; //第i位的值
        if(!son[o][y]) son[o][y] = ++iot;
        o = son[o][y];
        e[o]++;
    }
}
//查询Trie中与x异或的最大值
int checkmax(int x)
{
    int res = 0,o = 1;
    for(int i = 30;i >= 0;i --)
    {
        int y = x >> i & 1;
        if(son[o][!y]) o = son[o][!y],res |= (1ll << i);
        else o = son[o][y];
    }
    return res;
}
//查询最小值
int checkmin(int x)
{
    int res = 0,o = 1;
    for(int i = 30;i >= 0;i--)
    {
        int y = x >> i & 1;
        if(son[o][y]) o = son[o][y];
        else o = son[o][!y],res |= 1ll << i; 
    }
    return res;
}
//比x大的数目
int bigcount(int x)
{
    int res = 0,o = 1;
    for(int i = 30;i >= 0;i--)
    {
        int y = x >> i & 1;
        if(y == 0) res += e[son[o][1]];
        if(!son[o][y])break;
        o = son[o][y];
    }
    return res;
}
signed main()
{
 ios::sync_with_stdio(false);
 cin.tie(0),cout.tie(0);
    return 0;
}

关于异或问题,一般会想到前缀和 / 按位运算 / 01trie树/ 线性基
关于线性基,详见OI wiki
这里我介绍的算法都不是详细的,有问题可以去OI wiki的官网,直接搜就能搜的到

例题:算法模板题(题目很简单,就不放链接了)
KMP 和 Hash
在这里插入图片描述
KMP定义
在这里插入图片描述

manacher
在这里插入图片描述
字典树
在这里插入图片描述
01trie树
在这里插入图片描述
在这里插入图片描述
逆序对的数量这道题也可以使用树状数组来实现,等我讲到那个的吧

下面是一些有难度的题目

最大异或结点

链接
看上去像01,但是发现直接相连的不能这样取异或,所以要加入一个remove函数,每次要把相连的remove掉,但是还要记得复原

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define inf 0x3f3f3f3f
#define endl '\n'
#define int long long 
//思路 01tire写一个remove函数 修改边是不容易的,可以改权值
//将所有信息读入Trie中,对于每一个节点,移除掉与他有关的节点之后,取最大值,最后回溯,再将信息加进来
const int N = 3e6 + 1;
int son[N][2];
vector<int> v[N];//存放信息
int e[N];
int arr[N],fa[N];
int tot = 1;
void insert(int x)
{
   int o = 1;
   e[o]++;
   for(int i = 30;i >= 0; i--)
   {
    int y = x >> i & 1;
    if(!son[o][y]) son[o][y] =  ++tot;
    o = son[o][y];
    e[o]++;
   }
}
void remove(int x)
{
  int o = 1;
   e[o]--;
   for(int i = 30;i >= 0; i--)
   {
    int y = x >> i & 1;
    o = son[o][y];
    e[o]--;
   }
}
int query(int x)
{
  int res = 0;
  int o = 1;
  for(int i = 30;i >= 0; i--)
  {
    int y = x >> i & 1;
    if(son[o][!y] && e[son[o][!y]]) 
    {
        o = son[o][!y];
        res |= (1ll << i);
    }
   else o = son[o][y];
  }
  return res;
}
signed main()
{
 ios::sync_with_stdio(false);
 cin.tie(0),cout.tie(0);
 int n; cin >> n;
 for(int i = 0;i < n;i ++) cin >> arr[i];
 for(int i = 0;i < n;i++)
 {
    cin >> fa[i];
    if(fa[i] != -1)
    v[fa[i]].push_back(i - 1); //从0开始 v[0] 含有 1 3 
    // v[1] 含有2 4 
 }
 for(int i = 0;i < n;i++) insert(arr[i]);
  int ans = 0;
 for(int i = 0;i < n;i++)
 {
    //移除  //arr[i]是当前的节点 移除掉所有与arr[i]相连的节点 v[i]中存放好了个数
    //i - 1是当前节点  e + 1读入是 1 到 n  节点是0 到 n - 1;
   for(auto e : v[i]) remove(arr[e]);
    //计算
   ans = max(ans,query(arr[i]));
    //回溯
   for(auto e : v[i]) insert(arr[e]);
 }
 cout << ans;
    return 0;
}

串的前缀

串的前缀
利用并查集 + KMP

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 +10;
#define int long long
char s[N];
int n,ans;
int ne[N];
int find(int x)
{
    if(!ne[x]) return x;
    return ne[x] = find(ne[x]);
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin >> n >> s + 1;
   for(int i = 2,j = 0;i <= n;i++)
   {
     while(j && s[i] != s[j + 1]) j = ne[j];
     if(s[i] == s[j + 1]) j++;
     ne[i] = j;
   }
   //i   1 2 3 4 5 6 7 8
   //fin 1 2 1 2 1 2 1 2 
   //ans 0 0 2 2 4 4 6 6
   //nex 0 0 1 2 3 4 5 6 
    for(int i = 2;i <= n;i++)
    {
      if(ne[i]) ans += i - find(i);
    }
    cout << ans << endl;
    return 0;
}

你也喜欢幸运字符串吗

链接
dp数组的意义是dp[i]表示以0到i为前缀的个数
dp[nex[i]] = dp[nex[i]] + dp[i] //想一想就明白了,注意要倒着处理,因为nex数组
比如abcabc
dp[nex[6] = 3] += dp[6];

代码


```cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define inf 0x3f3f3f3f
#define endl '\n'
#define int long long 
const int N = 1e6 + 1;
char s[N];
int nex[N];
int dp[N];
signed main()
{
 ios::sync_with_stdio(false);
 cin.tie(0),cout.tie(0);
 int n; cin >> n;
cin >> s + 1;
for(int i = 2,j = 0;i <= n;i ++)
{
    while(j && s[i] != s[j + 1]) j = nex[j];
    if(s[i] == s[j + 1]) j++;
    nex[i] = j;
}
for(int i = 1;i <= n;i++) dp[i] = 1;
for(int i = n;i >= 1;i--) dp[nex[i]] += dp[i]; //加入前缀
int ans = 0;
for(int i = 1;i <= n;i++) ans += dp[i];
cout << ans;
    return 0;
}


异或和之差

链接
这个还是蛮恶心的
知道思路也写起来很费劲
就是对于每一个分割位置i,求出前面异或的最大 - 后面的最小 || 后面最大减去前面最小 记录四个数组开写。。。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define inf 0x3f3f3f3f
#define endl '\n'
#define int long long 
const int N = 1e6 + 1;
int sonl[N][2],sonr[N][2];
int premax[N],premin[N];
int sufmax[N],sufmin[N],arr[N];
int prexor[N],sufxor[N];
int totl = 2;
void insert(int son[][2],int x)
{
    int o = 1;
    for(int i = 20;i >= 0;i --)
    {
        int y = x >> i & 1;
        if(!son[o][y])
        {
            son[o][y] = totl++;
        }
        o = son[o][y];
    }
}
int checkmax(int son[][2],int x)
{
   int res = 0, o = 1;
   for(int i = 20;i >=0; i --)
   {
    int y = x >> i & 1;
    if(son[o][!y])
    {
        o = son[o][!y];
        res |= 1ll << i;
    }
    else o = son[o][y];
   }
   return res;
}
int checkmin(int son[][2],int x)
{
    int res = 0, o = 1;
    for(int i = 20;i >= 0;i --)
    {
        int y = x >> i & 1;
        if(son[o][y]) o = son[o][y];
        else
        {
            o = son[o][!y];
            res |= 1ll << i;
        }
    }
  return res;
}
signed main()
{
 ios::sync_with_stdio(false);
 cin.tie(0),cout.tie(0);
 int n; cin >> n;
 for(int i = 1;i <= n;i++) cin >> arr[i];
 insert(sonl,0);
 insert(sonr,0);

 for(int i = 0;i <= n + 1;i++)
 {
    premin[i] = sufmin[i] = inf;
 }
 for(int i = 1;i <= n;i++)
 {
    prexor[i] = prexor[i - 1] ^ arr[i];
    premax[i] = max(premax[i - 1],checkmax(sonl,prexor[i]));
    premin[i] = min(premin[i - 1],checkmin(sonl,prexor[i]));
    insert(sonl,prexor[i]);
 }
 for(int i = n;i >= 1;i --)
 {
    sufxor[i] = sufxor[i + 1] ^ arr[i];
    sufmax[i] = max(sufmax[i - 1],checkmax(sonr,sufxor[i]));
    sufmin[i] = min(sufmin[i - 1],checkmin(sonr,sufxor[i]));
    insert(sonr,sufxor[i]);
 }
 int ans = 0;
 for(int i = 1;i < n;i ++)
 {
    ans = max(ans,max(sufmax[i + 1] - premin[i],premax[i] - sufmin[i + 1]));
 }
 cout << ans;
    return 0;
}

到这吧,下次更新十三届A组真题 / 数论
求三连!!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值