洛谷 P2412 查单词
这一个题放在线段树里面显得非常清奇。很多题解并没有用线段树,或者是用的线段树方法很难。因此,这里为初学者献上一份较为简单容易看懂的代码。
题目大意
一共有\(n\)个单词和\(m\)次查询,其中第\(1\)~\(n\)行输入单词。前\(n\)行输入单词,后\(m\)行输入两个数\(l\),\(r\),表示求出区间\([l,r]\)内字典序最大的单词。
注意:在排序中不在乎大小写进行字典序排序,但输出须输出原单词。例如:abc和DEF,虽然大写的字典序要比小写的要小,但是由于在排序过程中不在乎大小写,因此答案还是输出DEF。同时,题目限时0.2秒。
解题思路
因为很多人看到题目中出现了限时0.2秒就放弃了线段树而采用更快的ST表,但是这道题加上线段树的标签是有原因的(那就是可以用线段树解)。
如果要用线段树,无非就是模板中的那几个模块:push_up,push_down,build,update,query。观察这道题,因为没有修改操作,只有查询操作,因此只保留push_up,build和query这三个模块。
需要的数组及变量
sum[]
数组:用于创建线段树。
a[]
数组:用于存储排序后的单词编号。
node{}
结构体:存储单词及其输入的编号。
tolower_str[]
数组:用于存储转换为小写后的单词及编号,同时用于排序。
ori_str[]
数组:用于存储原单词和编号,用于输出答案。
backup[]
数组:用于备份原数组。原数组后面可能需要进行数据调整,有一个备份始终比没有好。
n
,m
,x
,y
变量:题目所给。
设计代码
push_up()
函数
因为题目要求最大值,那么我们就维护一个树根保存的是最大值的线段树sum
。
因此,当前根节点的最大值就是这个节点左儿子和右儿子的最大值。
当然,我们的sum
存储的不是单词,而是单词的编号。找到了单词的编号,就能对应输出字典序最大的单词。
void push_up(int root) {
sum[root] = max(sum[root << 1], sum[root << 1 | 1]);
}
build()
函数
build()
函数就是建树,和模板一毛一样。
只是需要注意:a[]
数组需要保存的是排过序后的单词的编号(可以看↓下面的主函数)。
void build(int l, int r, int root) {
if (l == r) {
sum[root] = a[l];
return;
}
int mid = (l + r) >> 1;
build(l, mid, root << 1);
build(mid + 1, r, root << 1 | 1);
push_up(root);
}
query()
函数
我们建的树的树根维护的是最大字典序单词的编号,因此输出的答案也应该是取最大值。
所以,这道题的query()
函数和模板就没有区别,只是ans
那个地方取得是最大值。
因为返回的是最大字典序单词的编号,故函数的返回值是int
。
int query(int ledge, int redge, int l, int r, int root) {
if (ledge <= l && r <= redge)
return sum[root];
int mid = (l + r) >> 1;
int ans = -inf;
if (ledge <= mid)
ans = max(ans, query(ledge, redge, l, mid, root << 1));
if (redge > mid)
ans = max(ans, query(ledge, redge, mid + 1, r, root << 1 | 1));
return ans;
}
main()
主函数
正常读入就不多说了。
存储单词
首先,我们需要读入这个单词,是用临时字符串str来存储。
接下来,因为题目说要输出原单词而不是全部变成小写后的单词,我们需要将这个单词和编号一起存入ori_str[]
数组里(目的是为了输出原单词)。
之后,我们需要将这个单词转换成小写存在tolower_str[]
数组里面(因为题目说过:字典序大小不和字母大小写有关)。同时,把编号一起存入tolower_str[]
。
for (int i = 1; i <= n; i++) {
cin >> temp;
ori_str[i].s = temp;
ori_str[i].id = i;
backup[i] = ori_str[i];
transform(temp.begin(), temp.end(), temp.begin(), ::tolower);
tolower_str[i].s = temp;
tolower_str[i].id = i;
}
处理存储好的单词和建树
首先,已经转换好的单词就是用sort()
函数从小到大排序。(如果不清楚为什么要从小到大排序可以手动模拟一下样例数据)
sort(tolower_str + 1, tolower_str + 1 + n);
然后,ori_str[]
就是将排好序的单词进行存储。因为单词是要被从小到大处理一遍,也就是说单词的顺序可能会出现变化,这时候backup[]
数组的作用就体现出来了。它存储的是原始单词和编号,因此直接从backup[]
数组中以排好序后的顺序倒入到ori_str[]
数组中。
for (int i = 1; i <= n; i++)
ori_str[i] = backup[tolower_str[i].id];
最后,因为我们是以从小到大的顺序进行了单词的处理,因此我们现在需要将它的编号也以同样的顺序进行处理。
因为我们是以单词的编号进行建树,并且在处理过程中将单词以从小到大的顺序进行了排序,因此我们的单词编号也需要按照排序后的顺序进行存储,这样才能建正确的数。
可以这么理解:a[]
数组是我们的单词编号线段树,而ori_str[]
数组是我们的单词线段树。
for (int i = 1; i <= n; i++)
a[tolower_str[i].id] = i;
build(1, n, 1);
最后就是输出啦。因为我们的ori_str[]
数组就是我们的单词线段树,query()
函数就是寻找在\([l,r]\)区间内字典序最大的单词数的下标,因此直接读出ori_str[query()]
就是我们的答案。(这一步就是模板中的query()
,只是答案是string
必须要将下标返回给数组输出原单词)。
for (int i = 1; i <= m; i++) {
cin >> x >> y;
cout << ori_str[query(x, y, 1, n, 1)].s << endl;
}
完整代码
#include <bits/stdc++.h>
#define lson l, mid, root << 1
#define rson mid + 1, r, root << 1 | 1
#define lroot root << 1
#define rroot root << 1 | 1
#define inf 0x3f3f3f3f
using namespace std;
const int N = 5e4 + 10;
int n, m, a[N], x, y;
int sum[N << 2];
struct word {
string s;
int id;
bool operator < (const word W) const {
return s < W.s;
}
} tolower_str[N], ori_str[N], backup[N];
string temp;
void push_up(int root) {
sum[root] = max(sum[lroot], sum[rroot]);
}
void build(int l, int r, int root) {
if (l == r) {
sum[root] = a[l];
return;
}
int mid = (l + r) >> 1;
build(lson);
build(rson);
push_up(root);
}
int query(int ledge, int redge, int l, int r, int root) {
if (ledge <= l && r <= redge)
return sum[root];
int mid = (l + r) >> 1;
int ans = -inf;
if (ledge <= mid)
ans = max(ans, query(ledge, redge, lson));
if (redge > mid)
ans = max(ans, query(ledge, redge, rson));
return ans;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> temp;
ori_str[i].s = temp;
ori_str[i].id = i;
backup[i] = ori_str[i];
transform(temp.begin(), temp.end(), temp.begin(), ::tolower);
tolower_str[i].s = temp;
tolower_str[i].id = i;
}
sort(tolower_str + 1, tolower_str + 1 + n);
for (int i = 1; i <= n; i++)
ori_str[i] = backup[tolower_str[i].id];
for (int i = 1; i <= n; i++)
a[tolower_str[i].id] = i;
build(1, n, 1);
for (int i = 1; i <= m; i++) {
cin >> x >> y;
cout << ori_str[query(x, y, 1, n, 1)].s << endl;
}
return 0;
}
给个赞吧!