题目描述
小明正在分析一本小说中的人物相关性。他想知道在小说中 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 个字符。
注意:
Alice 和 Bob 是大小写敏感的,alice 或 bob 等并不计算在内。
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的重复遍历