题解 P1308 【统计单词数】
wxy_god
2018-09-13 12:51:11
[博客食用效果更佳哦](https://89396.blog.luogu.org/solution-p1308)
这题的做法太多了,但大部分其他题解都讲完了,下面的大概都是一些~~装逼用的做法~~
# 第一种做法 指针做法
先来介绍一下我的思路吧~
- 输入
- 统一转换成小写的字母
- 使用系统函数找出单词首次出现的位置,然后改变指针
- 不断统计答案
- 输出
[关于指针的更多介绍](https://89396.blog.luogu.org/c-zhi-zhen-ge-zhong-xuan-xue-cao-zuo)
这里使用了很多系统函数,在代码中详细介绍:
```
#include <cstring>
#include <cctype>
#include <cstdio>
void strlower (char *a) {//手写函数,将大写字母转换成小写字母
for(int i = 0; a[i]; i ++ ) {
if(isupper(a[i])) a[i] = tolower(a[i]);//isupper是判断是否是大写字母的系统函数,tolower是将其转换成小写字母的函数
}
}
int main () {
char destination[1000001], *q, source[11], *p;//destintion是要找的文章,source是要找的单词,p和q都是指针类,分别代表当前搜索到什么地方了和最后一次找到单词的指针
bool flag = false;//判断是否找到了
int ans = 0, ans1 = -1;//个数和首次出现的位置,ans1的初值是-1是因为在没找到的时候就直接输出就行了,省事
gets(source);
gets(destination);//输入
strlower(destination);//全部转换成小写字母
strlower(source);
int len = strlen(source);//长度,在后面防止越界和加快速度
p = destination;//先将指针设为全部
for(; q = strstr(p, source); ) {//循环,strstr是在一个字符串里面给定一个字符串,寻找有没有这个字符串,若有,返回首次出现的指针否则返回NULL(空指针)
if( q != NULL//找到了
&& ( q == destination || *(q - 1) == ' ') //第一个条件是防止越界,第二个是判断前一个是不是空格
&& ( *(q + len) == '\0' || *(q + len) == ' ' ) ) {//如果后面也是空格
ans ++ ;//答案加一
if(flag == false) {//如果是首次找到
flag = true;
ans1 = q - destination;//第一个位置
}
}
p = q + len;//刷新指针
}
if(flag == true)//找到了
printf("%d %d" , ans, ans1);//输出
else
printf("%d", ans1);//输出-1
return 0;
}
```
# 第二种做法 有穷自动机
图灵机大概就是一个“自动机”,就是说代码分好几种状态,每种状态做不同的事。
### 举个简单的例子吧
> 输入一个字符串,输入的只有两种字符,一种是字母,一种是空格。现在求一共有几个单词。注意,有可能有多个空格连在一起,开头和结尾都有可能有空格。
那么这是一道简单的有穷自动机,运行时分两种情况:
①是空格
②是字母
(其实当前状态就是上一个字符的状态
那么在遍历数组的时候拿一个变量记录下来当前是什么状态,可以用$0$代表当前是空格状态,$1$代表是字母状态
当如果当前状态是$1$,而现在却遇到空格,那么就计数器加一,**同时要将状态改为$0$**,如果当前状态是$0$,现在的字符却是字母,就只将状态改为$1$
### BUT!
在跳出循环的时候如果状态是$1$,要将计数器加一,否则如果最后是字母就会少统计一个单词!(想想为什么)
状态图:
![](https://cdn.luogu.com.cn/upload/pic/60060.png)
然后就是代码:
### 注:不是本题的代码!!!是这个例子的代码!!!
```
#include <cstdio>
int main () {
char a[1001];
int state, ans = 0;
gets(a);
if(a[0] == ' ') state = 0;//设置初始值
else state = 1;
for(int i = 1; a[i]; i ++ ) {//要从一开始遍历,因为零已经遍历过了
if(a[i] == ' ') {//是空格
if(state == 1) {//当前状态(前一个)是字母,说明找到一个单词了
ans ++ ;//答案加一
state = 0;//千万别忘了改状态
}
}
else {//是字母
if(state == 0) {//当前状态(前一个)是空格
state = 1;//将状态改为1
}
}
}
if(state == 1)//最后还要判断一下千万不要忘记
ans ++ ;
printf("%d", ans);
return 0;
}
```
那么,这就是简单的自动机代码,现在看看本题用自动机如何做
其实一样,就是注意字母状态分时要查找单词状态和不是要查找单词状态,而且单词第$n$个字母的状态就用$n$来表示
以下是code:
```
#include <cstdio>
#include <cctype>
#include <cstring>
const int SPACE = 0;//三种状态,这是空格状态
const int LETTER = -1;//字母状态,但这表示不是要查找的单词的字母的状态
const int WORD = 1;//而这种状态是要查找的单词的状态
//当然了,如果状态时大于1的数,说明是要查找的单词的中间部分的状态,上文讲过了
inline void strlower (char *a) {//不解释,上面的代码有了
for(int i = 0; a[i]; i ++ ) {
if(isupper(a[i])) a[i] = tolower(a[i]);
}
}
int main () {
char a[1000001], word[20];
int ans = 0;
int ans2 = -1;
int state = 0;//表状态,假设是空格,因为空格上来就判断是不是三种状态
int i;
gets(word);
gets(a);
strlower(a);
strlower(word);
int len = strlen( word );
for(i = 0; a[i]; i ++ ) {//遍历数组
switch ( state ) {
case SPACE : //如果当前状态(上一个)是空格
if(a[i] == word[0]) state = WORD;//变成单词第一个字母状态
else if(a[i] == ' ') state = SPACE;//其实这句话可以省略,因为反正都是空格状态,改它是一样的
else state = LETTER;//剩下的肯定是其他字母状态了
break;
case LETTER : //是其他字母状态
if(a[i] == ' ') state = SPACE;//空格状态
break;
default: //是要查找的单词状态
if ( state < len ) {//还不是最后一个字母
if(a[i] == ' ') state = SPACE;
else if(a[i] == word[state]) state ++ ;//变成下一个字母状态
else state = LETTER;//其他字母状态
}
else if (state == len )//是最后一个字母
{
if(a[i] == ' ') {//如果下一个是空格,找到了!
state = SPACE;//状态不要忘记改变
if(ans2 == -1)//第一次找到,记录下来位置
ans2 = i - len;//因为i是单词的尾,所以要减长度
ans ++ ;//个数加一
}
else state = LETTER;//可惜,最后跟着其他字母,不是单词
}
}
}
if(state == len) {
ans ++ ;
if(ans2 == -1)
ans2 = i - 1 - len;
}
if(ans2 == -1) printf("-1");
else printf("%d %d", ans, ans2);
return 0;
}
```
上面就是我用的两种AC做法
# 我的题解可能写得不太好,但你看的这么认真,不点个赞才走吗?