人物相关性分析(详解)

题目描述

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

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

例如以下文本:

This is a story about Alice and Bob.Alice wants to send a private message to Bob.

假设 KK = 20,则 Alice 和 Bob 同时出现了 2 次,分别是"Alice and Bob" 和 "Bob. Alice"。前者 Alice 和 Bob 之间有 5 个字符,后者有 2 个字符。

注意:

  1. Alice 和 Bob 是大小写敏感的,alice 或 bob 等并不计算在内。

  1. Alice 和 Bob 应为单独的单词,前后可以有标点符号和空格,但是不能 有字母。例如出现了 Bobbi 并不算出现了 Bob。

输入描述

第一行包含一个整数 K(1 \leq K \leq 10^6)K(1≤K≤106)。

第二行包含一行字符串,只包含大小写字母、标点符号和空格。长度不超过 10^6106

输出描述

输出一个整数,表示 Alice 和 Bob 同时出现的次数。

输入输出样例

示例
输入
20
This is a story about Alice and Bob.Alice wants to send a private
message to Bob.
输出
2

运行限制

  • 最大运行时间:1s

  • 最大运行内存: 512M

错解:首先没用gets,其次会超时,不过while(scanf("%d",&a))这个循环条件还是挺有用的

只不过字符类型会超时

//#include <stdio.h>
//#include <stdlib.h>
//int main(int argc, char *argv[])
//{
// int k,i=0,flag1,flag2,count=0;
// char* a[100];
// scanf("%d",&k);
  while(scanf("%d",&a))//类型啊你搞错了!!
// while(scanf("%s",&a[i]))
// //判断是否输出值范围太大也会超时.
// {
//   i++;
//   if(a=="Alice")
//   {
//     flag1=i;
//   }
//   if(a=="Bob")
//   {
//      flag2=i;
//   }
//   if(flag2>flag1&&flag2-flag1-1<=k)
//   {
//     count++;
//   }
// }
// printf("%d",count);
//  return 0;
//}

//用整形输出只能拿40分

//#include <stdio.h>
//#include <stdlib.h>
//int main(int argc, char *argv[])
//{
// int k,i=0,flag1,flag2,count=0;
// char a;
// scanf("%d",&k);
  while(scanf("%d",&a))//类型啊你搞错了!!
// while(scanf("%s",&a))
// //判断是否输出值非0范围太大也会超时.
// {
//   i++;
//   if(a=="Alice")
//   {
//     flag1=i;
//   }
//   if(a=="Bob")
//   {
//      flag2=i;
//   }
//   if(flag2>flag1&&flag2-flag1-1<=k)
//   {
//     count++;
//   }
// }
// printf("%d",count);
//  return 0;
//}
// 
// 

第二次看错题目,题目要求是一个字母一个字符,以为是字符串,不过可以当作字符串的解题题解

//当做是一个单词还有其他各种符号算一个字符的解法
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include<string.h>

char dp[1000000];
int main(int argc, char* argv[])
{
    char* set[1000000];
    int k, n = 0, flag1 = -1, flag2 = -1, count = 0;
    char a;
    scanf("%d", &k);
    //  while(scanf("%d",&a))//类型啊你搞错了!!
    gets(dp);
    set[n] = strtok(dp, " ");
    while (set[n] != NULL)
    {
        set[n++] = strtok(NULL, " ");
    }//以空格分割字符串,将空格单独计算,目的就是分割字符串
    for (int i = 0; i < n; i++)
    {


        if (set[i] == "Alice")
        {
            flag1 = i;
        }
        if (set[i] == "Bob")
        {
            flag2 = i;
        }//得出位置

        // if((2*flag2)-(2*flag1)-1<=k)
        // //你这里这么写。那你已经赋值的数字就会反复被判断,反复被使用,所以就会出错!
        // //而且flag没有被初始化,一开始都会count自增!
        // //所以你要初始化才行,而且要多设置条件判断!
        // {
        //   count++;

        // }
        if (flag1 != -1 && flag2 != -1)//保证出现了两个字符
        {

            if (((2 * flag2) - (2 * flag1) - 1) <= k)
          //里面包含了被分割的空格字符个数
            {
                count++;
                flag1 = -1;
                flag2 = -1;

            }

        }
        //懂了为什么你会错了,你没有读懂题意,
        //意思是This is a story about Alice and Bob.Alice 
        //中的Alice and Bob和Bob.Alice 这算两次,每当你质疑题目写错时
        //一定是你没有读懂题意!
        //这么看,那就是一个字母一个字符,空格那些也算字符



    }
    printf("%d", count);


    return 0;
}

后面想对原来的代码弥补,但还是太勉强了

//
题解
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include<string.h>

char dp[1000000];
int main(int argc, char* argv[])
{
    char* set[1000000];
    char* s[1000000];
    int k, n = 0, count = 0;
    char a;
    scanf("%d", &k);
    //  while(scanf("%d",&a))//类型啊你搞错了!!
    gets(dp);
    set[n] = strtok(dp, "Alice");
    while (set[n] != NULL)
    {
        set[n++] = strtok(NULL, "Alice");

    }
    n = 0;

    for (int i = 0; i < n; i++)
    {
        int flag = strlen(set[i]);
        if (flag <= k)
        {
            count++;
        }


    }
    printf("%d", count);


    return 0;
}

//尝试连续分割两个单词的字符串,然后统计中间字符串的长度,后来发现根本无法确定两个字符串之间的位置在哪里,放弃.
//虽然也可以先分割出一个单词,然后确定位置再分割一个一个统计,但太麻烦容易出错

//循环先分割然后定位两单词位置也不行,因为除了空格还可能有其他字符连在一起成为一个字符串,如Bob.Alice
//分割法太局限了,所以放弃分割法

正解

c++参考代码

循环超时

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
string str;
int k;
vector<int> a, b;//相当于数组

//这个检查Alice和Bob是否符合要求是单独的单词
bool check(char c) {
    return c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z';
}
int main() {
    cin >> k;
    getchar();
    getline(cin, str);
    for (int i = 0; i + 5 <= str.size(); i++)

        //虽然屏蔽了这个条件也对,但只是测试数据没有坑而已,
        //必要的if是必须要写的,如检查单独的单词!
        //开头的判断
        if (str.substr(i, 5) == "Alice")
            if (!i && !check(str[i + 5]) || !check(str[i - 1]) && !check(str[i + 5]) || !check(str[i - 1]) && i + 5 == str.size())
                //i==0时无需判断左边是否有字符
                //i+5没毛病,一开始从0下标开始,加5就到了e的右边
                //i为第一个字符下标,i-1前一个
                //三个条件分别为左中右

                a.push_back(i);//插入元素
              //substr,下标加长度(按右边推进)


    for (int i = 0; i + 3 <= str.size(); i++)
        // 用于检查独立单词是否符合题意,是上一个题解的补充

        if (str.substr(i, 3) == "Bob")
            if (!i && !check(str[i + 3]) || !check(str[i - 1]) && !check(str[i + 3]) || !check(str[i - 1]) && i + 3 == str.size())
                b.push_back(i);
    long long ans = 0;
    //下面的注释是参看y总的代码,分别找出Alice前面的Bob以及Bob前面的Alice
    //他们的和就是解
//    for(int i=0,l=0,r=-1;i<a.size();i++)//Alice前面有多少个Bob 
//    {
//        while(r+1<b.size()&&a[i]>=b[r+1])    r++;
//        while(l<=r&&a[i]-1-(b[l+1]+3)+1>k)    l++;
//        ans+=r-l+1;
//    }
//    for(int i=0,l=0,r=-1;i<b.size();i++)//Bob前面有多少个Alice 
//    {
//        while(r+1<a.size()&&b[i]>=a[r+1])    r++;
//        while(l<=r&&b[i]-1-(a[l+1]+5)+1>k)    l++;
//        ans+=r-l+1;
//    }
    //这个是参看了上一个题解替换了y总代码

    //元素个数的循环
    for (int i = 0, j = 0; i < a.size(); i++)
        //这里只是初始值,并不会每次循环都初始化!

    {  //遍历Alice数组元素            //首字母
        j = 0;

        while (j < b.size() && a[i] - k - 3 <= b[j] && b[j] <= a[i] + k + 5)
            ans++;   //维护窗口Bob


           

           //定一动一,固定Alice先,然后一个个核对bob,如果不满足条件就跳出循环
           //总是重置j运算量太大超时啦!!!所以说复杂的情况定一动一也不甚理想
    }

    cout << ans;
    return 0;
}

改进

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
string str;
int k;
vector<int> a, b;//相当于数组

//这个检查Alice和Bob是否符合要求是单独的单词
bool check(char c) {
    return c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z';
}
int main() {
    cin >> k;
    getchar();
    getline(cin, str);
    for (int i = 0; i + 5 <= str.size(); i++)

        //虽然屏蔽了这个条件也对,但只是测试数据没有坑而已,
        //必要的if是必须要写的,如检查单独的单词!
        //开头的判断
        if (str.substr(i, 5) == "Alice")
            if (!i && !check(str[i + 5]) || !check(str[i - 1]) && !check(str[i + 5]) || !check(str[i - 1]) && i + 5 == str.size())
                //i==0时无需判断左边是否有字符
                //i+5没毛病,一开始从0下标开始,加5就到了e的右边
                //i为第一个字符下标,i-1前一个
                //三个条件分别为左中右

                a.push_back(i);//插入元素
              //substr,下标加长度(按右边推进)


    for (int i = 0; i + 3 <= str.size(); i++)
        // 用于检查独立单词是否符合题意,是上一个题解的补充

        if (str.substr(i, 3) == "Bob")
            if (!i && !check(str[i + 3]) || !check(str[i - 1]) && !check(str[i + 3]) || !check(str[i - 1]) && i + 3 == str.size())
                b.push_back(i);
    long long ans = 0;


    //元素个数的循环
    for (int i = 0, l = 0, r = 0; i < a.size(); i++)

    {  //遍历Alice数组元素            //首字母
        while (l < b.size() && b[l] <= a[i] - k - 3)
            l++;   //维护窗口Bob
        while (r < b.size() && b[r] <= a[i] + k + 5)
            r++;   //维护右BOb
        ans += r - l;
             //答案加上窗口中元素个数 

       
    }
    cout << ans;
    return 0;
}

c语言解析

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int a[1000010]={0};
int b[1000010]={0};
  char str[1000010]={0};
  //不要自认为不可能,上限多高就要设多大,不然超过数组大小就会溢出运行就会错误
  //你把ab设成10^5,就有两个数据通不过!!
  //记住,运行错误不是超时,最可能就是溢出,如类型和数组大小,这些不确定的你就
  // 根据上限设大,或者long long,宁可设大也不设小.设到上限还要多几个也是防溢出.

  int check(char x)
  {
    return (x>='a'&&x<='z'||x>='A'&&x<='Z');
    // 自动判断,满足条件返回真值,否则假值
    // if语句&&必须所有都是真,一个假自动不进入if中
  }
  //判断是否为独立单词,左右两边没有多余的字母
  //只要你是这其中的字母,我就不要
  //但是其他字符就可以
  

int main(int argc, char *argv[])
{

int k,flag=0;
  long long ans=0;
  scanf("%d",&k);
   getchar();//吸收scanf的缓冲
  gets(str);
  //输入一行字符串
  long long len=strlen(str);
  //统计字符串长度

  for(long long i=0;i+5<=len;i++)
  {
    if(str[i]=='A'&&str[i+1]=='l'&&str[i+2]=='i'&&str[i+3]=='c'&&str[i+4]=='e')
    if(!i&&!check(str[i+5])||!check(str[i-1])&&!check(str[i+5])||!check(str[i-1])&&i+5==len)
       a[flag++]=i;
       //记录首字母出现的位置

        //i==0时无需判断左边是否有字符
                //i+5没毛病,一开始从0下标开始,加5就到了e的右边
                //i为第一个字符下标,i-1前一个
                //三个条件分别为左中右
  }
  // long long la=sizeof(a)/sizeof(a[0]);
  //这个算的总数是数组的整体大小,即使你没有赋值,
  //你总共开创的数组有多大总体就有多大,所以不能这么写
 long la =flag;
 //记录数组长度,为了记录出现了多少个Alice
  

  flag=0;//重置flag 

    for(long long i=0;i+3<=len;i++)
  {
    if(str[i]=='B'&&str[i+1]=='o'&&str[i+2]=='b')
    if(!i&&!check(str[i+3])||!check(str[i-1])&&!check(str[i+3])||!check(str[i-1])&&i+3==len)
       b[flag++]=i;
  }
long lb =flag;//记录
  

  for(long long i=0,l=0,r=0;i<la;i++)
  //先固定Alice,然后判断与Bob位置

  {//注意是查找到哪个字母算不超过,据题意只要碰到了一个单词都算不超过
  //你的想法是对的,旁边连续的BOb如Alice and Bob.Bob也是两次
    while(l<lb&&b[l]+2<a[i]-k)//A左边的Bob
    //因为记录的是他们的首字母,我们需要根据首尾单词的位置判断
    
    l++;
    while(r<lb&&a[i]+4+k>=b[r])//右边的
    r++;
    ans+=r-l;

    // 整个维护过程就是l减r增,如果l不增,说明Bob都在范围内,因此每一次的Alice都能加上这个Bob
    // 所以r不用重置和初始化,每一次循环都加上一次,一旦l增说明超出范围,此时A不能再加,
    // 因此减去.而每次循环的Alice都是越来越后的,所以l也不用重置,每一次都要减去这一次的l
    //同时也要保留加上这个r(因为存在循环时l和r都不变的情况)
    // 同时l和r一直记录着减去和增加的数量,每一次的Alice都是直接加在ans上的,因此这个操作只是针对
    // 每一次,对于每一次而言,我们只需要把满足条件的Bob新加进来,不满足的减去
    // 也就是r-l了。前面累积的结果就是通过l与r的记录表示.

    // 如果总是从头遍历,每次从第一个Bob开始就会超时,这么写的好处在于检索的Bob和Alice一直都是
    // 向前推进的,没有回头,避免重复的检索.

   //简言之就是每一个Alice,满足的就加进来,不满足就舍去,左边舍去,右边加
   //同时l与r还充当遍历下标,只要l与r增加我就可以再向后遍历一个Bob看看满不满足
   //因为旁边连续的BOb如Alice and Bob.Bob也是两次


  }
  printf("%lld",ans);
  
  return 0;
}

// 总结:l与r分开记录就是为了单独识别左边和右边的判断推进,如果r增,说明有A能够在范围内,他们就算同时出现一次
// 所以r可以推进到下一个Bob,也就是以后我都不需要再遍历这个Bob了,这就避免了重复遍历
// 同理l推进也是,当出现Bob脱离当前A的范围,
//也可以推进了,因为当前的A都已经脱离,那么后面的也一定脱离,就避免了对前面Bob的重复遍历

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值