题目
题意概要
你在聆听戴夫的教诲。戴夫的一句话可以抽象为一个二进制串,因为可以听出来的词太少。两句话的“匹配度”是它们的最长公共前缀。
对于戴夫的一句话,你会在记忆中搜寻他已说过的一句,满足二者的“匹配度”最高。将这个他已经说过的话称为“引言”。现在每秒钟可能发生两个事件中的一个:
- 戴夫说了一句话,你把它给记住了。
- 戴夫说了一句话 S S S 。你感觉似乎之前听过。你想知道,从第 a a a 秒开始,一直到第 b b b 秒,随着你记住的话不断变多, S S S 的“引言”改变了多少次?但是你不会记住这个事件中的话,因为你忙着回忆了。
一共有
M
M
M 秒,把每个事件二的答案都告诉戴夫吧!虽然他听不懂。
数据范围与提示
M
≤
1
0
6
M\le 10^6
M≤106 。
思路
高级 の の の暴力
先不管题目到底在问什么,只考虑怎样求 当前某个地址的匹配(并且可以快速加入)。
发现就是 0 - 1 t r i e 0\text{-}1\;\tt{trie} 0-1trie 树板题。暴力加入。 O ( M 2 ) \mathcal O(M^2) O(M2) 。肯定过不了。
单调栈优化
慢就慢在查询上。
由于没有删地址的操作,某个地址的匹配一定是越来越优,所以这些匹配一定是在 t r i e \tt{trie} trie 树的某条链上。
每次用一个单调栈,维护时间的单调性,最后查栈里有几个 t ∈ [ a , b ] t\in [a,b] t∈[a,b] 。
单次操作 O ( 32 ) \mathcal O(32) O(32) ,战斗结束。
代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
inline int readint(){
int a = 0, f = 1; char c = getchar();
for(; c<'0' or c>'9'; c=getchar())
if(c == '-') f = -1;
for(; '0'<=c and c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
class Trie{
struct Node{
Node* son[2];
int data;
Node(){
son[0] = son[1] = NULL;
data = 0;
}
};
Node* root;
public:
Trie(){ root = new Node(); }
void Add(unsigned x,int len,int id){
Node* o = root;
for(int i=31; i>31-len; --i){
int d = x>>i&1;
if(o->son[d] == NULL)
o->son[d] = new Node();
o = o->son[d];
}
o->data = id;
}
vector<int> Query(unsigned x){
vector<int> v; v.clear();
Node* o = root;
for(int i=31; ~i; --i){
int d = x>>i&1;
if(o->son[d] == NULL)
break;
o = o->son[d];
if(o->data){
while(not v.empty() and o->data < v.back())
v.pop_back();
v.push_back(o->data);
}
}
return v;
}
}xez;
int main(){
int T = readint();
for(int i=1,zxy=1,ppl; i<=T; ++i){
char cmd = getchar();
ppl = 0;
for(int j=0; j<4; ++j)
ppl = (ppl<<8|readint());
if(cmd == 'A'){
int len = readint();
xez.Add(ppl,len,zxy ++);
}
if(cmd == 'Q'){
vector<int> &&v = xez.Query(ppl);
int cnt = 0, a = readint(), b = readint();
for(; not v.empty(); v.pop_back())
if(a <= v.back() and v.back() <= b)
++ cnt;
printf("%d\n",cnt);
}
}
return 0;
}