zoukankan      html  css  js  c++  java
  • 蓝桥杯省赛 *3

    一下题目均可在Acwing找到。

    人物相关性分析  https://www.dotcpp.com/oj/problem2309.html

    小明正在分析一本小说中的人物相关性。他想知道在小说中 Alice 和 Bob 有多少次同时出现。

    更准确的说,小明定义 Alice 和 Bob“同时出现”的意思是:在小说文本 中 Alice 和 Bob 之间不超过 K 个字符。

    例如以下文本:

    This is a story about Alice and Bob. Alice wants to send a private message to Bob. 假设 K = 20,则 Alice 和 Bob 同时出现了 2 次,分别是”Alice and Bob”

    和”Bob. Alice”。前者 Alice 和 Bob 之间有 5 个字符,后者有 2 个字符。 注意:

    1. Alice 和 Bob 是大小写敏感的,alice 或 bob 等并不计算在内。
    2. Alice 和 Bob 应为单独的单词,前后可以有标点符号和空格,但是不能

    有字母。例如 Bobbi 並不算出现了 Bob。


    思路:

      最容易想到的是枚举Alice和Bob在文本出现次数,构成两个整数数组,接着二重遍历求解,时间复杂度O(n^2)。但字符串输入长度范围是1e6,如果出现

    如AliceBob连续出现的极端情况,时间就达到1e10量级,超时(上界在1e8左右)。

      如何由二重循环--->一重?一般有二分,哈希等方法。更进一步理解题意:观察某个首字母在 i 的Alice:与同时出现的Bob可能的范围在[ i - K - 3 , i + 5 + K ]。

    而Alice的位置是单调递增的,所以同时出现的Bob其位置也是单调递增的,此时可以采用尺取法求解问题。尺取法通常是指对数组保存一对下标(起点,终点),

    然后根据实际情况交替推进两个端点直到得出答案的方法,类似一段区间缓慢向前移动。

    解题思路:

      首先分别求出Alice与Bob出现在文本中的位置,用下标 lp 保存第一次出现在可能范围的Bob,rp保存最后一次出现在范围的Bob,用 rp - lp + 1 即得到与

    当前Alice同时出现的Bob的个数。

    实现代码:

    const int INF = 1e8;
    
    int main()
    {
            /*输入*/
        int K;
        string str;
        cin>>K;
        getchar();
        getline(cin,str); //输入带有空格的一行字符串 
        
        vector<int> alice,bob;
        /*保存出现的位置*/
        bob.push_back(-INF); /*统一计算*/
        int pos = 0;
        while( (pos = str.find("Bob",pos)) != string::npos ) //遍历Bob在str出现位置
        {
            bob.push_back(pos);
            pos++;    
        } 
        bob.push_back(INF);
        pos = 0;
        while( (pos = str.find("Alice",pos))!=string::npos )
        {
            alice.push_back(pos);
            pos++;
        }
        
            /*尺取法*/
        int lp = 0, rp = 0;
        int cize = alice.size();
        long long ans = 0;
        for( int i=0; i<cize; i++ )
        {
            while( bob[lp]<alice[i]-K-3 )     lp++;
            while( bob[rp+1]<=alice[i]+K+5 ) rp++; 
            if( rp-lp+1 > 0 )    ans += rp - lp + 1;
        }
        cout<<ans<<endl;
        return 0;
    }    

    这里的做法不够严格,不能避免Bobb情况。需要在判断Bob后面的那个单词不是字母。

    /*
     *人物相关性分析 
     *    简单思路是找到Alice与Bob首字母出现个数 然后双重循环判断,当然超时
     *    蓝桥杯经常有枚举解决但会超时的题目 一个思路是减少枚举的变量
     *    其他变量用二分、哈希或者其他方法减少枚举次数
     *    而这一题我们观察到 对于Alice的位置 其Bob与其相关的区间为
     *    [Ai-3-K, Ai+5+k] Ai递增 所以区间也是不断向右移动的 可以用尺取法解决
     *        找到位置要注意单词的独立 
     */
    #include<cctype>
    #include<cstdio>
    #include<vector>
    #include<cstring>
    using namespace std;
    
    typedef long long ll;
    
    const int INF     = 1e8+10;
    const int Max_N = 1e6+10;
    
    int k;
    char s[Max_N];
    int length;
    vector<int> alice, bob; //保存位置 
    
    /*用很笨的方法判断 如果参数是指针会超时*/
    bool checkA(int i)//Alice
    {
        if( length - i < 5 )    return false;
        return (s[i+1]=='l'&&s[i+2]=='i' && s[i+3]=='c' && s[i+4]=='e' && !isalpha(s[i+5]) );
    }
    bool checkB(int i)//Bob
    {
        if( length - i < 3 )    return false;
        return (s[i+1]=='o'&&s[i+2]=='b' && !isalpha(s[i+3]) );    
    }
    
    int main()
    {
        scanf("%d",&k);
        getchar();
        gets(s);
        length = strlen(s);
        bob.push_back(-INF); 
        //bob位置在两端加上[-INF,INF] 统一操作 不需要判断下标是否越界 
        for( int i=0; i<length; i++ )
        {
            if( s[i]=='A' && checkA(i) )
            {
                alice.push_back(i);
                i += 5;
            }
            else if( s[i]=='B' && checkB(i) )    
            {
                bob.push_back(i);
                i += 3;
            }
        }
        bob.push_back(INF);
        //尺取法
        int lp = 0, rp = 0; //[lp,rp)内符合条件 
        ll ans = 0;
        for( int i=0, cize=alice.size(); i<cize; i++ )
        {
            while( alice[i] > bob[lp] + 3 + k )        lp++;
            while( bob[rp] <= alice[i] + 5 + k )    rp++;
            if( rp - lp >0 )    ans += rp - lp ;    
        } 
        printf("%lld
    ",ans);
        return 0;
    }

    字串分值 

      对于一个字符串S,我们定义S的分值 f(S) 为S中七号出现一次的字符串个数。例如 f("aba") = 1,f("abc") = 3,f("aaa") = 0。

      现给定一个字符串S[ 0, 1, ..., n-1 ](长度为n),请你计算对于所有 S 的非空字串 S[ i, ..., j ](i<=j<n),f(S[ i, ..., j ])的和是多少。

      输入一行包含一个由小写字符组成的字符串S。


    思路:

      最简单的思路即枚举所有字串,优化方案是固定 i ,每次递增 j ,并用一个哈希表判断新的字符是否出现过,时间复杂度O(n^2)。

      面临超时问题。观察字符串S = ababc

      字串        f值

      a           1

      ab            2

        aba          1

      abab        0 

      ababc        1

       b          1

       ba         2

       bab         1

       babc          2

         a           1

         ab         2

       abc         3

         b        1

         bc         2

           c            1

      其中加粗的表示分值项。按题意思路是求每行字符串的分值,不可避免的需要遍历所有字串。换个角度,最终目标是求分值和,我们可以求每个字符每列的分值。

    如对第一个a,其分值是2。那么如何计算每个字符列分值和呢?观察第二个字符a,其计算分值的行是没有其他字符a的行,那么只要求其前一个a出现的位置 l 与后一个

    a出现的位置 r ,那么该a的分值为( i - l )*( r - i )(其中 i 为这个a的下标,那么a的左边有 i - l种可能 × a右边有 r - i 种可能)。

      如何只看加粗字母的计数,题目思路可以视为从先行后列计数变为先列后行计数,从而只要记录相同字母第一次在其位置之前与之后的位置即可,跳出了遍历所有可能。

      (l-i) * (r-i)是因为对于第二个a,其左边界可以是ba、右边界可以是abc。

      注意用ans记录和时需要先把(l-i)转为long long,否则可能溢出,比如字母只出现在中间位置,则(1e5/2)*(1e5/2)对于int溢出。

    实现代码:

    #include<iostream>
    #include<string>
    using namespace std;
    
    const int Max_N = 100000;
    
    int last[26]; //last[i] : 字符 i+a 最近一次出现的位置 
    int pre[Max_N]; //pre[i]:i左侧第一个与str[i]位置相同的位置 默认-1 
    int nxt[Max_N]; //nxt[i]:i右侧第一个与str[i]位置相同的位置 默认 length(str长度) 
    
    int main()
    {
        string str;
        cin>>str;
        int length = str.length();
        
        /*求pre[]:默认值为-1*/
        for( int i=0; i<26; i++ )    last[i] = -1;
        for( int i=0; i<length; i++ )
        {
            int x   = str[i] - 'a';
            pre[i]  = last[x];
            last[x] = i;
        }
        /*求nxt[]:默认值为length*/
        for( int i=0; i<26; i++ )    last[i] = length;
        for( int i=length-1; i>=0; i-- )
        {
            int x   = str[i] - 'a';
            nxt[i]  = last[x];
            last[x] = i;
        }
        /*求解*/
        long long ans = 0;
        for( int i=0; i<length; i++ )
        {
            ans += (long long)(i-pre[i])*(nxt[i]-i);//!!
        }
        cout<<ans<<endl;
        return 0;
    }

    字串分值和

    思路:

      与上一题类似,唯一变化的是 f 值的定义。仍然可以用求每个字符列值和的思路,稍有变化的是对于每个字符,其出现多次的分值为1而不是0。

    比如aba,前一个a与后一个a总和分值为1,那么给哪一个字符加粗表示其为分值呢?我们可以认为规定一个:对于一个字串种多次出现的字符,我们

    只给最左侧加粗即作为分值。

      那么对于每个字符,只需要考虑其左侧第一个与其相同的位置而不用考虑右侧,因为对于右侧它的分值都为1。

    实现代码:

      

    #include<iostream>
    #include<string>
    using namespace std;
    
    const int Max_N = 100000;
    
    int last[26]; //last[i] : 字符 i+a 最近一次出现的位置 
    int pre[Max_N]; //pre[i]:i左侧第一个与str[i]位置相同的位置 默认-1  
    
    int main()
    {
        string str;
        cin>>str;
        int length = str.length();
        
        /*求pre[]:默认值为-1*/
        for( int i=0; i<26; i++ )    last[i] = -1;
        for( int i=0; i<length; i++ )
        {
            int x   = str[i] - 'a';
            pre[i]  = last[x];
            last[x] = i;
        }
        long long ans = 0;
        for( int i=0; i<length; i++ )
        {
            ans += (long long)(i-pre[i])*(length-i);
        }
        cout<<ans<<endl;
        return 0;
    }

    修改数组  https://www.dotcpp.com/oj/problem2301.html

    给定一个长度为 N 的数组 A = [A1, A2, · · · AN ],数组中有可能有重复出现 的整数。

    现在小明要按以下方法将其修改为没有重复整数的数组。小明会依次修改 A2,A3,··· ,AN。

    当修改 Ai 时,小明会检查 Ai 是否在 A1 ∼ Ai−1 中出现过。如果出现过,则 小明会给 Ai 加上 1 ;如果新的 Ai 仍在之前出现过,小明会持续给 Ai 加 1 ,直 到 Ai 没有在 A1 ∼ Ai−1 中出现过。

    当 AN 也经过上述修改之后,显然 A 数组中就没有重复的整数了。 现在给定初始的 A 数组,请你计算出最终的 A 数组。


    思路

      一个简单的思路是模拟:用一个哈希表判断数子 A[ i ] 是否出现过,若出现则 A[ i ] 自增1。其问题仍然是时间复杂度:一个极端情况是A1到AN均为1,那么

    时间复杂度有一次来到了O(n^2)。

      那么能否一步求得A[ i ]的值呢?考虑维护以及存在整数的区间,如[1,3 ],[ 17,25 ],[27,27]

      当处理Ai时:

        查找是否有区间覆盖A[ i ]

         若不存在,则A[ i ]不变

         若存在则将A[ i ] 加入集合

        将A[ i ]插入集合,处理可能导致的区间合并

      那么问题就变成如何实现维护区间的高效数据结构/算法,以及如何更新维护的区间。

    具体实现

      可以用set<pair<int,int>>维护区间集合,其中pair<int,int>以左右区间端点形式表示区间。

      用upper_bound解决是否有区间覆盖A[ i ];而合并区间A,B可以先删去A,B,在加入A,B合并后的区间。

      因为set是用平衡树实现,所以插入删除操作时间复杂度O(logN),而查找upper_bound是用二分法实现,其时间复杂度也为O(logN),最终实现的时间复杂度为O(NlogN)。

    实现代码

    #include<algorithm>
    #include<utility>
    #include<cstdio>
    #include<set>
    using namespace std;
    
    typedef set< pair<int,int> > set_pair;
    
    const int INF   = 1e8; 
    const int Max_N = 100000;
    
    int a[Max_N];
    
    int main()
    {
        int N;
        scanf("%d",&N);
        for( int i=0; i<N; i++ )    scanf("%d",&a[i]);
        
        set_pair set_;
        set_.insert(make_pair(-INF,-INF));
        set_.insert(make_pair(INF,INF)); //作为区间的两端 统一算法 关键是第一个值 
        set_.insert(make_pair(a[0],a[0]));
        
        for( int i=1; i<N; i++ )
        {
            /* it.first是第一个>a[i]的区间  pre->first<=a[i](小于或等于)*/
            set_pair::iterator it = set_.upper_bound(make_pair(a[i],INF));
            set_pair::iterator pre = it; pre--;
            
            if( pre->second>=a[i] )
            {//有交集   否则pre->second<a[i]且pre->first<=a[i] 那么a[i]与pre无交集 
                a[i] = pre->second+1;//更新a[i] 
            }
            
            if( a[i]==pre->second+1 && a[i]==it->first-1 )
            {//合并两个区间 
                set_.insert(make_pair(pre->first,it->second));
                set_.erase(pre); set_.erase(it);
            }
            else if( a[i]==pre->second+1 )
            {//只与pre有交集 
                set_.insert(make_pair(pre->first,a[i]));
                set_.erase(pre);
            }    
            else if( a[i]==it->first-1 )
            {//只与it有交集 
                set_.insert(make_pair(a[i],it->second));
                set_.erase(it);
            }
            else
            {//不与先有区间有交集 
                set_.insert(make_pair(a[i],a[i]));
            }
        }
        
        for( int i=0; i<N; i++ )
        {
            printf("%d ",a[i]);
        }
        printf("
    ");
        
        return 0;
    }

       

  • 相关阅读:
    Java io 理解
    Java应用的理解
    Flyweight 享元模式
    Bridge 桥梁模式
    Decrator 装饰模式
    [CF997C]Sky Full of Stars_二项式反演_等比数列_容斥原理
    [CF1010D]Mars Over_位运算性质
    [CF991D]Bishwock_状压dp
    [Agc030B]Tree Burning_贪心
    [Cometoj#4 E]公共子序列_贪心_树状数组_动态规划
  • 原文地址:https://www.cnblogs.com/w-like-code/p/14530277.html
Copyright © 2011-2022 走看看