当前位置: 首页 > news >正文

【蓝桥杯】每日练习 Day12 贡献法

前言

今天给大家带来两道贡献法的问题,先来讲一下什么是贡献法

贡献法,与其说是一种算法,不如说是一种数学方法,是一种思维方式

先来给大家举个例子,假设现在有个问题,需要你在一个只有小写字母的字符串中查找所有仅包含一个字符a子串数量

暴力的话是枚举所有子串,O(n^2)显然不符合我们的预期,那么有什么方法优化呢?

这就要用到我们今天要讲的贡献法了,这一种横看成岭侧成峰的算法,我们来观察下面的样例。

bbbbbacccc

可以发现在中间位置出现了a,我们将a的左右两侧划分出来。

bbbbb|a|cccc

问题就转化成了在a左边选一个字母,同时在a右边选一个字母的所有选法。

我们设左边的字符数量为left,右边的字符数量为right,那么根据排列组合的知识可以得到子串数量为:

(left + 1) * (right + 1)

注意这里要加一因为a也要算进去。

以上就是贡献法的大致思路,所谓横看成岭侧成峰,就是从结果出发,逆推出有效的结论

可能有的小伙伴发现了我们这个题目有一个要求,即——仅包含一个字符a

那我们就来想一下,如果没有这个条件,贡献法可不可行呢?

答案是否定的,我们来观察下面的序列

bbbbabbabbb

可以明显的发现最长的串是同时包含两个a的,而我们使用贡献法就需要分别求出包含指定a的所有子串,我们将这些子串视为一个集合

在计算完所有包含指定的a的所有子串后我们要进行集合合并,而根据容斥原理,集合合并时需要减去两个集合的交集,显然这个交集我们是不好求出来的。

造成这种情况的原因就是两个集合不是互斥的,放在概率论中就可以说两个条件不是独立的

所以在判断能否使用贡献法之前需要先判断一下给定的条件能否使每个元素的贡献皆相互独立

反之,如果发现条件能够使整个结果的集合划分为几个部分,那么就可以使用贡献法。(反应在题目中一般就是“唯一”,“单个”等词)

纸上学来终得钱,接下来我们就根据具体的题目分析,一起来看看所谓的“贡献法”到底有何魔力。


孤独的照片


分析

暴力枚举所有区间的话时间复杂度是O(n^2),显然行不通,但是我们发现所有的孤独的照片都有一个特点,即——同时包含两种牛,并且其中有一种牛的数量为1

很敏锐的发现题目中有唯一的条件,那么可不可以用我们上面讲的贡献法来写呢?

使用贡献法需要满足一个条件,即——我们选定的贡献的对象可以将要求的集合划分

很显然是可以的,因为两头可能孤独的牛不会出现在同一张“孤独的照片”内所以任意两头牛产生的贡献的交集为空集,所以我们可以使用贡献法。

那么接下来我们就来思考一下如何使用贡献法。

根据上面的思路,我们需要通过排列组合来每头牛的贡献就要保证当前区间只有一头此类牛。

这个简单,我们预处理出每头牛左边和右边的同类牛的位置(使用扫描法),就可以计算出对于每头牛的最长的“孤独的照片”,随后进行排列组合就好了。

这道题排列组合有一个小技巧,因为有一个要求最短区间长度为3,所以我们需要分类讨论该位置在区间两边和在区间中间的情况。


代码

/*
    贡献法
*/
#include<iostream>
#include<cstring>
using namespace std;
typedef long long LL;
const int N = 500010;
char s[N];
int n;
int l[N], r[N];
LL ans;

LL find(char x)
{
    int m = 0;
    memset(l, 0, sizeof(l)); memset(r, 0, sizeof(r));
    for(int i = 1; i <= n; i++)
        if(s[i] == x)
            l[i] = m, m = i;
    m = n + 1;
    for(int i = n; i >= 1; i--)
        if(s[i] == x)
            r[i] = m, m = i;
            
    // 预处理
    LL ls = 0;
    for(int i = 1; i <= n; i++)
    {
        if(s[i] == x) //匹配了
        {
            int left = i - l[i] - 1, right = r[i] - i - 1; //左右长度
            //printf("%d %d %d ", i, left, right);
            ls += max((LL)0, (LL)left * right);
            ls += max(0, left - 1);
            ls += max(0, right - 1);
        }
    }
    return ls;
}

int main()
{
    scanf("%d%s", &n, s + 1);
    ans += find('G');
    ans += find('H');
    printf("%lld", ans);
    
    return 0;
}

子串分值


分析

可以发现题目中有唯一的要求,所以我们考虑使用贡献法

那么,接下来我们要如何来划分呢?

这个简单,我们通过26个英文字母来划分,因为f函数是求贡献和,所以我们每次只求单个字符的贡献,最后加起来就一定是贡献和。

但是如果这道题是要我们判断区间内存在个数为1的字符的区间的数量,显然就无法直接用贡献法求解了,需要进一步的分析。


代码

// 贡献法,枚举26个字母,贡献求和
#include<iostream>
using namespace std;
typedef long long LL;
const int N = 100010;
char s[N];
int n, l[N], r[N];
LL idx;
LL find(char x)
{
    int m = 0; LL idx = 0;
    for(int i = 1; i <= n; i++)
        if(s[i] == x)
            l[i] = m, m = i;
    m = n + 1;
    for(int i = n; i >= 1; i--)
        if(s[i] == x)
            r[i] = m, m = i;
    
    for(int i = 1; i <= n; i++)
    {
        if(s[i] == x)
        {
            int left = i - l[i] - 1, right = r[i] - i - 1;
            idx += ((LL)left + 1) * (right + 1);
        }
    }
    return idx;
}

int main()
{
    scanf("%s", s + 1);
    for(int i = 1; s[i]; n = i++);
    
    for(char x = 'a'; x <= 'z'; x++)
        idx += find(x);
    printf("%lld", idx);
    return 0;
}

相关文章:

  • vulhub靶场jangow-01-1.0.1
  • 数据结构之循环队列的顺序结构基本操作-基本结构-初始化-入队-出队-判断队列是否为空-获取队头元素
  • 当AI重构编程范式:Java 24的进化逻辑与技术深水区的战略突围
  • 【ESP32S3】esp32获取串口数据并通过http上传到前端
  • 造成服务器网络连接不稳定的原因是什么?
  • 【Python】pillow库学习笔记2-ImageFilter类和ImageEnhance类
  • PHP If...Else 语句详解
  • 用户模块——自定义业务异常
  • 【react】实现路由返回拦截的多种方式
  • Three学习入门(四)
  • DeepSeek助力文案,智能音箱如何改变你的生活?
  • 【企业网络安全防护】一体化管控平台:企业办公效率与安全兼得!
  • 计算机专业本科毕业设计一般有哪些选题?
  • 突破反爬困境:SDK架构设计,为什么选择独立服务模式(四)
  • Web前端之JavaScript的DOM操作冷门API
  • 一个简单的用C#实现的分布式雪花ID算法
  • OpenCV图像拼接(6)根据权重图对源图像进行归一化处理函数normalizeUsingWeightMap()
  • 鸿蒙第三方解析(一)
  • 3. 轴指令(omron 机器自动化控制器)——>MC_GearIn
  • ollama迁移已下载的单个模型到服务器
  • 怎样给网站做推广/seo排名优化方式
  • 哈尔滨龙彩做网站多少钱/软文推广
  • 婚纱摄影网站seo方案/软文网
  • 网站建设彳金手指排名/今日头条淄博新闻
  • wordpress登录后台闪退/整站优化关键词排名
  • php动态网站开发试题/互联网项目推广平台有哪些