题解 P5335 [THUSC2016]补退选
\[\text{题目大意}
\]
\(\quad\)本题就是每次对一个字符执行 \(3\) 种操作:
- 操作1:插入一个字符串 \(s\) 。
- 操作2:弹出一个字符串 \(s\) 。
- 操作3:询问以字符串 \(s\) 为前缀的数量最早在哪个事件后 超过 \(x\) 。
注意:
- 同一时刻可以存在多个完全相同的字符串。
- 保证删除的数存在。
- 操作的编号就是事件编号,包括操作1,2,3。
- 另外操作强制在线,上一次的答案要取绝对值。
- 好像要开 long long,否则会WA
\(\quad\)因为是询问字符串存在的问题,考虑使用字典树求解,用 \(sz_i\) 表示编号为 \(i\) 的点,每次插入字符串路径上的点 \(sz_i+1\) ,删除字符串时字符串路径上的点 \(sz_i-1\) (基本操作)。
\(\quad\)因为字符串前缀数量一定是逐渐变大,不可能一次从 \(1\) 个加到 \(10\) 个,所以用 vector num_{i,j} 存储字符串编号为 \(i\) 的点第一次超过 \(j\) 数量的最早的事件编号,询问时先判断历史上是否存在,如果 vector 的大小小于 \(x\),就返回。
本题是真的有点水
//P5335 [THUSC2016]补退选
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<vector>
#define int long long
#define re register int
#define il inline
using namespace std;
il int read()
{
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)&&ch!='-')ch=getchar();
if(ch=='-')f=-1,ch=getchar();
while(isdigit(ch))x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
return x*f;
}
il void print(int x)
{
if(x<0)putchar('-'),x=-x;
if(x/10)print(x/10);
putchar(x%10+'0');
}
const int N=1e5+5,M=6e6+5;
int n,ch[M][10],last,sz[M],cnt;
vector<int>num[M];
char s[65];
il void insert(int id)
{
scanf("%s",s+1);
int len=strlen(s+1),u=0;
for(re i=1;i<=len;i++)
{
int x=s[i]-'a';
if(!ch[u][x])ch[u][x]=++cnt;
u=ch[u][x];sz[u]++;
if(sz[u]>num[u].size())num[u].push_back(id);//第一次超过
}
}
il void del()
{
scanf("%s",s+1);
int len=strlen(s+1),u=0;
for(re i=1;i<=len;i++)
{
int x=s[i]-'a';
u=ch[u][x];sz[u]--;
}
}
il void query()
{
scanf("%s",s+1);
int len=strlen(s+1),u=0;
int x=read(),y=read(),z=read();
x=(x*last+y)%z;
for(re i=1;i<=len;i++)
{
int x=s[i]-'a';
if(!ch[u][x]){last=1;puts("-1");return;}
u=ch[u][x];
}
if((long long)num[u].size()>x)print(last=num[u][x]),putchar('\n');
else puts("-1"),last=1;
}
signed main()
{
n=read();
for(re i=1;i<=n;i++)
{
int op=read();
if(op==1)insert(i);
else if(op==2)del();
else query();
}
return 0;
}