题解 P5335 [THUSC2016]补退选

\[\text{题目大意} \]

\(\quad\)本题就是每次对一个字符执行 \(3\) 种操作:

  • 操作1:插入一个字符串 \(s\)
  • 操作2:弹出一个字符串 \(s\)
  • 操作3:询问以字符串 \(s\) 为前缀的数量最早在哪个事件后 超过 \(x\)

注意:

  1. 同一时刻可以存在多个完全相同的字符串。
  2. 保证删除的数存在。
  3. 操作的编号就是事件编号,包括操作1,2,3。
  4. 另外操作强制在线,上一次的答案要取绝对值。
  5. 好像要开 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;
}
原文地址:https://www.cnblogs.com/FarkasW/p/15463151.html