二分答案,滚动hash,kmp,hash(不使用stl)(下)

目录:

一.二分答案

二.滚动hash

三.kmp,hash(不使用stl)(下)


 一.二分答案法:
 1.性质与概念:
 二分答案的思想源于二分查找,二分答案思想最大的好处在,
 从直接求答案,变成了利用“夹逼”的方法试出最佳答案 
 对于一个求最优解的问题,拆分成:枚举一个解 + 判定此解是否正确
(正确不一定是最优)
通过遍历解空间 + 判定的方法找到最优解,
当解空间具有单调性时,便可以使用二分


 2.题:
 LeetCode 410 分割数组的最大值:
 给定一个非负整数数组 nums 和一个整数 m ,
 你需要将这个数组分成 m 个非空的连续子数组。
设计一个算法使得这 m 个子数组各自和的最大值最小。
 #1:
 输入:nums = [7,2,5,10,8], m = 2
 输出:18
思路:
通过枚举解空间,并以二分的方式来“夹逼”,来找到最优解。
1)找到解空间的上下界
找上下界时,不需要思考该解是否正确,只需卡住定义域即可
上界:最大的解,就是不分组,全部数的和
下界:最小的解,也就一个数一个组,为最大的那个数
2)依据二分所需求的单调性,写出判定函数
二分要求的单调性为:函数不严格单调。
同时,对于解空间,随着解的增大,数组中所分的组数也就越少
为了设计出基于解空间不严格单调的判定函数,于是
判定函数:给定一个解T,若m个子数组的各自的和的最大值 <= T,则这个T是正确的
T就是分组时每个组的和的上界,只要这个组的和没超过T,就继续往这个组内添加数。
设当前所分的组的数量为k,题目给定的组数为m
于是依据T进行分组之后,k会随T的增大而减小
T > allsum时,k <= m,返回true,告诉程序T大了,为使其减小,我们左移r来减小上界
T < allsum时,k > m,返回false,告诉程序T小了,为使其增大,我们右移l来增大上界
代码实现:

#include<iostream>
#include<vector>
using namespace std;
vector<int>nums;
bool fun(vector<int>& nums, int x, int m)
{
    int ans = 0, sum = 0;
    for (int i = 0; i < nums.size(); i++)
    {
        if (nums[i] > x)return false; //下界:一个数一组
        sum += nums[i];    //上界:全部一组
        if (sum > x){
            sum = nums[i];
            ans++;                    //代表当前分组数
        }
    }
     /*cout<<x<<" "<<ans<<endl;*/
    if (ans + 1 <= m)return true;
    return false;
}
int splitArray(vector<int>& nums, int m) {
    int l = 0, r = 1e9, fin = -1;
    while (l <= r)
    {
        int mid = (l + r) / 2;
        if (fun(nums, mid, m))
        {
            r = mid - 1;
            fin = mid;
        }
        else l = mid + 1;
    }
    return fin;
}
int main()
{
    int n, m;
    ios::sync_with_stdio(false);
    cin >> n;
    while (n--) {
        int num;
        cin >> num;
        nums.push_back(num);
    }
    cin >> m;
    cout << splitArray(nums, m) << '\n';
    return 0;
}


 洛谷P2678 [NOIP2015 提高组] 跳石头
这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。
组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,
有 N 块岩石(不含起点和终点的岩石)。
在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。
为了提高比赛难度,组委会计划移走一些岩石,
使得选手们在比赛过程中的最短跳跃距离尽可能长。
由于预算限制,组委会至多从起点和终点之间移走 M 块岩石
(不能移走起点和终点的岩石)。
输入:
第一行包含三个整数 L, N, M分别表示起点到终点的距离,起点和终点之间的岩石数,
以及组委会至多移走的岩石数。保证 L≥1 且 N≥M≥0。
接下来 N行,每行一个整数,第 i 行的整数 D_i(0 < D_i < L)
 表示第i块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,
且不会有两个岩石出现在同一个位置。
输出:
一个整数,即最短跳跃距离的最大值。

#include<iostream>
using namespace std;
const int maxn = 500010;
int n, m, ans, mid, d;
int a[maxn];
bool judge(int x) {
    int tot = 0; //需要移动的总数
    int i = 0;
    int now = 0;//当前所在的石头
    while (i < n+1) {
        i++;
        //如果当前石头与下一块石头之间的距离比我们设定的最短的距离要小,
        //  那么这块石头就得移走.
        //tot++代表需要移动的加一, 
        //i++代表接下来判断移动的石头后面的一块与当前`now`石头的距离
        if (a[i] - a[now] < x) {
            //那么就将这块石头拿走
            tot++;
        }
        else {
            //如果满足的话
            now = i;
        }
    }
    //如果需要移动比最大可移动数目还要多的石头, 那么return false
    if (tot > m) return false;
    return true;
}
int splitArray(int a[], int m) {
   int  l = 1, r = d;
    while (l <= r) {
        //二分枚举
        mid = (l + r) / 2;
        if (judge(mid)) {
            //mid是可行解的情况
            ans = mid;
            l = mid + 1;
        }
        //mid不是可行解的情况
        else r = mid - 1;
    }
    return ans;
}
int main() {
    ios::sync_with_stdio(false);
    cin >> d >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    a[n + 1] = d;
    cout << splitArray(a, m) << '\n';
    return 0;
}

 丢瓶盖
 题目描述
陶陶是个贪玩的孩子,他在地上丢了A个瓶盖,为了简化问题,
我们可以当作这A个瓶盖丢在一条直线上,现在他想从这些瓶盖里找出B个,
使得距离最近的2个距离最大,他想知道,最大可以到多少呢?
输入格式
第一行,两个整数,A, B。(B <= A <= 100000)
第二行,A个整数,分别为这A个瓶盖坐标。
输出格式
仅一个整数,为所求答案。
输入输出样例
输入 #1
5 3
1 2 3 4 5
输出 #1
2
思路:
 第一个瓶盖是必选的,之后贪心的选择第一个能让距离大于等于所check的答案,
 然后再以这个瓶盖继续贪心选择下一个。之后判断选择的瓶盖数量。

#include<iostream>
using namespace std;
int n, m, a[100005], l, r, mid, ans;
inline bool check(int x)
{
    int num = 1, last = 1;
    for (int i = 2; i <= n; i++)
    {
        if (a[i] - a[last] >= x) {
            last = i;
            num++;
        }

    }
    return num < m;//x太大不可以 
}
int splitArray(int a[], int m) {
    l = 1, r = 1e9;
    int fin = -1;
    while (l < r)
    {
        mid = l + r >> 1;
        if (check(mid)) {
            r = mid - 1;
            fin = mid;
        }
        else l = mid + 1;
    }
    if(check(fin))return  fin-1;
    return fin;
}
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    cout << splitArray(a, m) << endl;
    return 0;
}

二.滚动hash(感觉就是进制hash+查询子串hash值):
 1.作用:
 判断两个字符串是否相同,往往是通过比较两个字符串的哈希
 常用语言中计算字符串哈希的方法往往是一个字符一个字符的计算,导致计算字符串哈希的时间复杂度是O(C),其中C是字符串的长度。
如果我们要计算字符串长度为C的子串的哈希,时间复杂度为O(NC)。
我们可以利用前缀的思想,采用滚动哈希。
滚动哈希就是一种O(1)时间复杂度的计算子串哈希的方法。


 2.步骤:
 (1).第一步要计算一个字符串的hashCode
 我们以一下方式来定义hashCode
 字母a ~ z用数字1 ~ 26来计算hash Code
我们利用一个数b,来作为各个位数上的区分,一般b取131或1331等质数
 例如:对于单词apple
 有a = 1,p = 16,l = 12,e = 5
hashCode(apple) = 1 * pow(131, 0) + 16 * pow(131, 1) + 16 * pow(131, 2) + 12 * pow(131, 3) + 5 * pow(131, 4)
找到一个数p,每次求hashCode时,都需要对p取模
p是用来规避哈希冲突的,所以p要比较大,一般根据题目要求来界定
 (2).求出任意一段连续子串的hashCode,其想法类似于前缀和:
 对于前缀和数组s[]:欲求从l到r的和
 sum(l, r) = s[r] - s[l - 1]
 同理若求出了整个字符串的hashCode
 hash[r]与hash[l - 1]存在一定关系
 hash[r]与hash[l - 1]中间相差了一定长度的字符串,其长度为r - l + 1
 所以通过求出pow(b, r - l + 1),就可以求出从l到r的hashCode
 hashCode(l, r) = hash[r] - hash[l - 1] * pow(b, r - l + 1)
 但注意的是,还需要对p取模,并且由于是hashCode之间相减,得到的hashCode有可能是负数
所以hashCode(l, r) = ((hash[r] - hash[l - 1] * pow(b, r - l + 1)) % p + p) % p
其实就是时钟的思想:三点之前的八小时是几点。其实就是七点;这里模数为12;
 3-8=-5;
 -5+12=7;

 三.kmp,hash(不使用stl解法)(下)
密文搜索:
hash解法:
 思路:
起初沿用之前的思路,无法用substr截取特定个数字符串,便想用string特性加,之后快排字符串,
得到排后hash值,但发现,快排字符串太麻烦且对string不好排序,并且一但转换
char类型,hash值得到函数又要发生改变,于是转换思路:
 因为密码可以全排列,于是只要密码中与对照密码字母相同,位置可不同
 ans就可以++
 转换为hash值,于是每八位就可以建立联系,不必每次确定上限与下限,只要
 将首八位按照进制hash的方式储存,之后就可以
 后八位-前八位第一位的hash值+后八位最后一位的hash值得到
 hash值的得到起初想用%mod+mod)%mod的方式,可报错,于是用s[i]*s[i]方式
 依旧报错,于是采用s[i]*s[i]*s[i]的方式
 判断部分:用二分查找之后,还需遍历key[i],因为可能存在多个解
hash+二分+快排
 

#include<iostream>
#include<cstring>
using namespace std;
typedef unsigned long long ull;
const int MAXN = 100000010;
char s[MAXN];
int key[MAXN],flag=0;
char s1[10];
ull ans = 0;
inline int my_sort(int l, int r)  //快排
{
    if (l >= r) 
        return 0;
    int i = l, j = r;
    int t, t1= key[l];
    while (i < j)
    {
        while (i < j && key[j] >= t1) 
            j--;
        while (i < j && key[i] <= t1) 
            i++;
        if (i < j){
            t = key[i]; 
            key[i] = key[j];
            key[j] = t;
        }
    }
    key[l] = key[i];
    key[i] = t1;
    my_sort(l, i - 1);
    my_sort(i + 1, r);
}
inline int my_search(int l, int r, int x)   //二分查找
{
    if (l > r) 
        return -1;
    if (key[l] == x) 
        return l;
    if (key[r] == x) 
        return r;
    int mid = (l + r) / 2;
    if (key[mid] == x) 
        return mid;
    else if (key[mid] > x) 
        my_search(l, mid - 1, x);
    else 
        my_search(mid + 1, r, x);
}
inline int my_find(char s1[])      //判断部分
{
    int i, k = 0, j, sum = 0;
    for (i = 0; i < 8; i++){
        k += (s1[i]) * s1[i] * s1[i];
    }
    j = my_search(0, flag - 1, k);
    if (j == -1) 
        return 0;
    for (i = j - 1; i >= 0 &&key[i] == k; i--)
        sum++;
    for (i = j + 1; i < flag &&key[i] == k; i++) 
        sum++;
    return sum + 1;
}
inline void get_hash() {
    int i, j;
    j = 1;
    for (i = 8; s[i]; i++) {
        key[j] = key[j - 1] - (s[i - 8] * s[i - 8] * (s[i - 8])) + (s[i]) * s[i] * s[i];
        j++;
    }
    flag = j;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> s;
    int i, j;
    for (i = 0; i < 8; i++){
        key[0] += (s[i]) * s[i] * s[i];
    }
    get_hash();
    my_sort(0, flag - 1);
    int n;
    cin >> n;
    for (i = 0; i < n; i++){
        cin >> s1;
        ans += my_find(s1);
    }
    cout << ans << '\n';
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值