目录
板块零:输入输出流
阴间的输入和输出。
情况一:读取字符串和读取行混用的时候
#include <bits/stdc++.h>
using namespace std;
int main() {
string a, b;
cin >> a;
cin.get();//清除回车,否则下面的getline会因为cin残留的回车符直接结束,输出回车
getline(cin, b);
cout << a << '\n';
cout << b << '\n';
return 0;
}
cin会残留回车符,就像scanf一样。
情况二:关于识别空行
#include <bits/stdc++.h>
using namespace std;
int main() {
string a;
getline(cin, a);
if (a == "") {
cout << "YES\n";
} else {
cout << "NO\n";
}
return 0;
}
getline对空行畅行无阻,并不会停下来,不会异常返回。
专治阴间输入输出流练习题:
第一题:hdoj2072
注意坑点可能有多个空格,主要这里是每行一组,以#结束。
#include <bits/stdc++.h>
using namespace std;
int main() {
char ch;
set<string> s;
string a;
int flag = 0;//如果前面有空格的话
while ((ch = getchar()) != '#') {//如果不是#
if (ch <= 'z' && ch >= 'a') {
if (flag) {//如果后面有字母,而且,前面是有空格
s.insert(a);
flag = 0;
a = "";
}
a += ch;
} else if (ch == ' ') {//如果最开始就是空格
if (a != "") {//而且,a不是空串,flag等于1
flag = 1;
}
} else if (ch == '\n') {//如果读到回车
if (a != "") {//而且还有一个串
s.insert(a);
a = "";
flag = 0;//注意置零以防对下一组数据产生影响
}
cout << s.size() << '\n';
s.clear();//清空
}
}
return 0;
}
情况三:用char数组接受getline函数的输入流
getline()函数是只能接受string类型的。 不信可以试试:p
#include <iostream>
#include <string>
using namespace std;
int main() {
string a;
char b[1000];
getline(cin, a);
return 0;
}
情况四:关于汉字
汉字占两个char,而且,两个char变量的值均为负数。
#include <bits/stdc++.h>
using namespace std;
const int MAX = 1e6 + 5;
char s[MAX];
int main() {
int t;
cin >> t;
cin.get();
while (t--) {
gets(s + 1);
long long ans = 0;
for (int i = 1; i < strlen(s + 1); i++) {
if (s[i] >= 0 && s[i] <= 127) continue;
ans++;
i++;
}
cout << ans << '\n';
}
return 0;
}
补充练习:
第一题:单词反转(对行,pe地狱)
#include <bits/stdc++.h>
using namespace std;
int main() {
int t;
cin >> t;
getchar();
while (t--) {
string s = "";
char ch;
while ((ch = getchar()) != '\n') {
if (ch == ' ') {
for (int i =s.size() - 1; i >= 0; i--) {
if (s[i] == ' ') continue;//小心空格
cout << s[i];
}
cout << ' ';
s = "";
}
s += ch;
}
for (int i = s.size() - 1; i >= 0; i--) {
if (s[i] == ' ') continue;//小心空格
cout << s[i];
}
cout << '\n';
}
return 0;
}
情况五:关于getchar()读取到文件尾
第一题:排序
注意while ((ch = getchar()) != EOF) ch和getchar之间需要括号,否则优先级会出问题。
#include <bits/stdc++.h>
using namespace std;
int main() {
// ios::sync_with_stdio(false);
// cin.tie(0);
priority_queue<int, vector<int>, greater<int> > q;
char ch;
int flag = 0, sum = 0;
while ((ch = getchar()) != EOF) {
if (ch != '5' && ch != '\n') {
flag = 1;
}
if (flag && ch != '5' && ch != '\n') {
sum = 10 * sum + ch - '0';
}
if (flag && ch == '5') {
flag = 0;
q.push(sum);
sum = 0;
}
if (ch == '\n') {
if (flag) {
q.push(sum);
sum = 0;
flag = 0;
}
int c = q.top();
q.pop();
cout << c;
while (!q.empty()) {
c = q.top();
q.pop();
cout << ' ' << c;
}
cout << '\n';
}
}
return 0;
}
板块一:字典树(Trie)
字典树是一种前缀树。有邻接矩阵版本,指针链表版本和结构体版本(大概)。
注意:
关于数组开多大的问题,注意,这种方法的空间消耗很大。最大的空间不是最长子串而是所有的字母数,即单词数 * 最长单词长度,这样才稳妥不会RE。
操作1:建树
板子:
void insert(string s) {
int p = 0;
for (int i = 0; i < s.size(); i++) {
int x = s[i] - 'a';
if (trie[p][x] == 0) trie[p][x] = ++id;//如果这个点不存在则标记id结点
p = trie[p][x];//这个类似链表中的空指针,不断移动,每次从根结点开始
}//id是指向下一个要填的字母的位置!!!
cnt[p]++;//标记,记录在最后一个字母的id上。id和p之间一追一赶十分精巧。
}
trie是一个二维数组,不是字符数组,有点类似邻接矩阵。其中一维是26个字母的标号,第二维是节点的编号用变量p表示,id是一个编号,用来表示单词的先后关系,用来说明此处是否有单词。cnt数组用来统计此处的单词数目。
这个id的使用是关键,当trie走到零的时候,说明这是一个之前没有走到过的一个新的节点,id++,输入第一个单词的时候,比如“abc”id是变化是1 2 3 然后我们再次输入 cba的话,id 变化是 4 5 6但是如果输入的是a b d的话,则是输入a的时候会索引到“ab”的b的p是2,然后到d的时候p到了3,此时是d然后出现了一个新的编号7
下面是p右边是x | 0(a) | 1(b) | 2(c) | 3(d) |
0 | id = 1 1 | 4 | ||
1(p = id) | 2 2 | |||
2 | 3 | 7 | ||
3 | ||||
4 | 5 | |||
5 | 6 | |||
6 | ||||
7 |
三种颜色表示了三次输入,填表的过程。id是独立且唯一的,我们给前缀不同的单词进行标号,每当出现一种新的前缀就给一个新的编号。这种写法感觉空间的浪费还是有点大的。
操作2:查找
int find(string s) {
int p = 0;
for (int i = 0; i < s.size(); i++) {
int x = s[i] - 'a';//注意大小写
if (trie[p][x] == 0) return 0;
p = trie[p][x];
}
return cnt[p];//标记
}
和建树基本相同。
封装成结构体:
来自oiwiki略作改动。
struct Trie {
int nex[100000][26], cnt;
bool book[100000]; // 该结点结尾的字符串是否存在
void init() {//初始化
memset(nex, 0, sizeof(nex));
memset(book, 0, sizeof(book));
cnt = 0;
}
void insert(string s) { // 插入字符串
int p = 0;//类似指针的一个数
for (int i = 0; i < s.size(); i++) {
int c = s[i] - 'a';//注意大小写,如果是单词大写的话会RE(负数越界)
if (!nex[p][c]) nex[p][c] = ++cnt; // 如果没有,就添加结点
p = nex[p][c];//同上
}
book[p] = 1;//标记
}
bool find(string s) { // 查找字符串
int p = 0;
for (int i = 0; i < s.size(); i++) {
int c = s[i] - 'a';
if (!nex[p][c]) return 0;
p = nex[p][c];//逐个比对
}
return book[p];//返回
}
} trie;
习题集:
第一题:统计Trie的结点数
裸题,注意单词的大小写!
#include <bits/stdc++.h>
using namespace std;
struct Trie {
int nex[100000][26], cnt;
bool book[100000]; // 该结点结尾的字符串是否存在
void init() {//初始化
memset(nex, 0, sizeof(nex));
memset(book, 0, sizeof(book));
cnt = 0;
}
void insert(string s) { // 插入字符串
int p = 0;//类似指针的一个数
for (int i = 0; i < s.size(); i++) {
int c = s[i] - 'A';//注意大小写,如果是单词大写的话会RE(负数越界)
if (!nex[p][c]) nex[p][c] = ++cnt; // 如果没有,就添加结点
p = nex[p][c];//同上
}
book[p] = 1;//
}
bool find(string s) { // 查找字符串
int p = 0;
for (int i = 0; i < s.size(); i++) {
int c = s[i] - 'A';
if (!nex[p][c]) return 0;
p = nex[p][c];
}
return book[p];
}
} trie;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
string s;
trie.init();
while (cin >> s) {
trie.insert(s);
}
cout << trie.cnt + 1 << '\n';//有一个root
return 0;
}
板块二:AC自动机
概念和原理:
AC自动机:Aho-Corasick automaton,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法。(并不能自动AC: P
批注1:AC自动机是基于Trie树的数据结构结合KMP算法的算法。
应用:一个常见的例子就是给出n个单词,再给出一段包含m个字符的文章,让你找出有多少个单词在文章里出现过。