A题: Browser Games
原题链接:https://ac.nowcoder.com/acm/contest/11261/A
题目大意
本题的空间限制只有32MB。
有
n
(
1
≤
n
≤
1
0
5
)
n(1\le n\le 10^5)
n(1≤n≤105) 个网页游戏在接下来
n
n
n 天发布,每天发布一个,用户必须用指定url打开才能玩游戏,url长度不超过
100
100
100 。
但服务器设置有问题,一旦用户以某种方式得到了未发布游戏的url,游戏数据就会泄露。为此决定每天在服务器端设置一组确认前缀,确认前缀是非空字符串,当请求的url与游戏的url相同且至少有一个确认前缀是url的前缀时,服务器才会正确响应,否则将返回未找到游戏。
求出每天所需的最少确认前缀数。
题解
根据题目描述,我们不难发现一个规律:时间越往后,已有的前缀只会变短。
我们可以标记每个前缀可用的时间段(区间修改采用差分的方式),但显然直接枚举是不行的,我们可以采取递归:若我们需要查询
1
1
1 ~
k
k
k 位前缀相同的字符串,我们可以只在
1
1
1 ~
k
−
1
k-1
k−1 位已经相同的字符串中查找,以减少复杂度。
我们初始可以记录每个游戏发布的时间,然后根据字典序对url进行排序,那么若干位前缀相同的url肯定会构成一个区间,然后每次对于新的一位的相同情况再次划分区间(即分治思想)递归即可(若有区间相同(如
1
1
1 ~
k
k
k 位均相同的都属于同一个区间,则对
1
1
1 位相同,
1
1
1 ~
2
2
2 位相同,…不重复进行差分区间修改)的适用前缀则去重)。
详见代码注释(注意区间为左闭右开)
参考代码
#include<bits/stdc++.h>
#define For(i,n,m) for(int i=n;i<=m;i++)
#define FOR(i,n,m) for(int i=n;i>=m;i--)
#define ss(s) scanf(" %s",s)
using namespace std;
void read(int &x){
int ret=0;
char c=getchar(),last=' ';
while(!isdigit(c))last=c,c=getchar();
while(isdigit(c))ret=ret*10+c-'0',c=getchar();
x=last=='-'?-ret:ret;
}
const int MAXN=1e5+5,MAXM=1e2+5;
int n,date[MAXN],ans[MAXN];
char s[MAXN][MAXM];
bool cmp(int a,int b){return strcmp(s[a]+1,s[b]+1)<0;}//字符串字典序比较
void solve(int l,int r,int end,int k){//[l,r)表示在date表中查询的范围,end表示l~r区间中前k-1位相同的最迟date+1(即差分结束位)
if(l==r)return;//递归边界返回
int maxn=0;//maxn存储1~k位相同的最迟date(相同前缀的字符串只有全部出现之后,这个前缀才能使用)
for(int i=l,last=l;i<=r;i++){//last记录当前第k位相同的字符串的区间起始位
maxn=max(maxn,date[i]);//记录最迟date(注意如果1 ~ k位前缀相同和1 ~ k-1位前缀相同的区间相同,则maxn最终会与end相等,即最终i=r差分修改时会在同一位加减(相当于没有修改),实现自动去重)
if(i==r||s[date[i]][k]!=s[date[i+1]][k]){//若前缀发生改变(注意i=r也是前缀改变,因为区间[l,r)右开,date[l ~ r-1]与date[r]的1 ~ k-1前缀已经不同)
ans[maxn]++,ans[end]--;//差分处理(右开区间,差分结束位用end即可)
solve(last,i,maxn,k+1);//对[last,i)区间继续递归,差分结束位为maxn(maxn已经归入上一行代码的差分区间中了)
last=i+1,maxn=0;//新区间的起始位last为i+1,重置maxn
}
}
}
int main()
{
read(n);
For(i,1,n){
ss(s[i]+1);//读入下标从1开始
date[i]=i;//标记日期
}
sort(date+1,date+n+1,cmp);//根据字符串字典序对date进行排序(排序的目的是使得相似(字典序相近)的字符串的date可以靠的更近(构成区间))
solve(1,n+1,n+1,1);//solve区间[1,n+1),即[1,n]
For(i,2,n)ans[i]+=ans[i-1];//差分跑出前缀和
For(i,1,n)printf("%d\n",ans[i]);
return 0;
}