【我的板子】前缀函数

一、点击查看参考及定义

给定一个长度为 n 的字符串 s,其前缀函数被定义为一个长度为 n 的数组 pi 。
主要就是来求一个字符串从第一个字符开始的每一个子串的前缀后缀相等的最长长度。
比如:求abcab的前缀函数求得就是
a的前缀后缀最长的长度:1
ab的前缀后缀最长的长度:0
abc的前缀后缀最长的长度:0
abca的前缀后缀最长的长度:1
abcab的前缀后缀最长的长度:2
所以要求一个字符串的前缀后缀相等的最长的长度可以用前缀函数

二、计算前缀函数的朴素算法

只需要了解s.substr(0, j)是用来截取s字符串的从下标为0的字符开始的j个字符的,下面的代码就很好理解,至于为什么第二个循环是从i开始呢,是因为遍历到i时,也就是要求0-i这段字符串的最长公共前后缀的长度,那它最大就是i

vector<int> prefix_function(string s) {
  int n = (int)s.length();
  vector<int> pi(n);
  for (int i = 1; i < n; i++)
    for (int j = i; j >= 0; j--)  // improved: j=i => j=pi[i-1]+1
      if (s.substr(0, j) == s.substr(i - j + 1, j)) {
        pi[i] = j;
        break;
      }
  return pi;
}

三、前缀函数的第一个优化

这里跟朴素算法的区别是进行了范围缩小,我们可以利用0~i-1这段字符串来推出0~i这段字符串的最长公共前后缀。
比如:
下标: 0 1 2 3 4 5
字符串:a b c a b c
比如我们现在要求i=5的pi值,那么这个值要么比pi[4]大一(abcabc),要么减少(abdabc、ababa),要么维持不变(aabaaa)所以他的最大值是pi[4]
+1

vector<int> prefix_function(string s) {
  int n = (int)s.length();
  vector<int> pi(n);
  for (int i = 1; i < n; i++)
    for (int j = pi[i - 1] + 1; j >= 0; j--)  // improved: j=i => j=pi[i-1]+1
      if (s.substr(0, j) == s.substr(i - j + 1, j)) {
        pi[i] = j;
        break;
      }
  return pi;
}

四、第二个优化

我们发现前两个算法中枚举最长长度j每次是递减1的,但其实不用,我们可以发现如下图所示,要求第i+1个pi值,我们首先从pi[i]+1开始,如果s[pi[i]]即图中s4等于s[i+1]的话,pi[i+1]就等于pi[i]+1,但是如果不行等的话,我们也是没必要逐一递减的,假设j是0~i的第二最长公共前后缀,那么我们只需要去比较s[j]和s[i+1],这样我们就会发现我们其实可以跳着递减j去枚举

在这里插入图片描述

vector<int> prefix_function(string s) {
  int n = (int)s.length();
  vector<int> pi(n);
  for (int i = 1; i < n; i++) {
    int j = pi[i - 1];
    while (j > 0 && s[i] != s[j]) j = pi[j - 1];
    if (s[i] == s[j]) j++;
    pi[i] = j;
  }
  return pi;
}
### 蓝桥杯 C/C++ 1ms 延时函数实现 对于蓝桥杯竞赛中的单片机编程,通常使用的是8051系列微控制器。为了实现精确的1毫秒延时功能,在给定的工作频率下可以通过计算循环次数来达到所需的延迟时间。 假设系统晶振频率为12 MHz,则机器周期为1 μs。下面是一个基于此条件下的1 ms延时子程序: ```c #include "reg52.h" void DelayMs(unsigned int time) { unsigned int i, j; while(time--) { for(i = 246; i > 0; i--) { // 外层循环控制总的等待时间 for(j = 113; j > 0; j--); // 内层空操作循环提供更精细的时间调整 } } } ``` 上述代码通过双重嵌套for循环实现了大约1毫秒的延时效果[^1]。需要注意的是,实际应用中应根据具体的硬件平台和编译环境适当调节内外两重循环变量初值以获得最接近理论值的实际延时长度。 此外,还可以利用定时器中断的方式来完成更加精准可靠的延时任务。这里给出另一种方案作为参考: ```c #define FREQ 12000000L /* 定义系统的晶体震荡频率 */ #define TMR_PRESCALE 12 /* 设定预分频系数 */ // 初始化T0计数器用于产生固定间隔的定时溢出事件 void Timer0Init(void){ TMOD |= 0x01; // 设置模式1(16位自动重装载) TH0 = (65536-(FREQ/TMR_PRESCALE/1000)); TL0 = TH0; } // 开启定时器并启动一次性的1ms延时 void StartDelay1ms(void){ TR0 = 1; // 启动定时器 ET0 = 1; // 允许接收来自T0的中断请求 EA = 1; // 总控开关打开允许所有外部中断源工作 TF0 = 0; // 清除可能存在的未处理标志位 } // 中断服务例程ISR负责重新加载计数值从而形成连续脉冲序列 void Timer0_ISR(void) interrupt 1{ static bit flag=0; TH0 = (65536-(FREQ/TMR_PRESCALE/1000)); TL0 = TH0; if(flag==1){ // 当前处于奇数次调用则返回 flag=0; return ; } flag=1; // 下次进入偶数状态 } ``` 这段代码展示了如何配置定时器以及编写相应的中断服务程序(ISR),以便于每次触发时都能保持固定的1 ms间隔回调[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值