题目大意 : 每天发行一个游戏,当固定前缀里有URL前缀时,这个游戏可以下载,所以我们要添加前缀保证第i天之前的游戏都可以下载(包含URL的前缀),之后的都不能下载(不能包含ULR的前缀)。
思路 : 我们添加的前缀肯定是越来越短的才来保证数量最少(假如有两个前缀有共同前缀,当算到第二个前缀,我们添加一个这两个前缀的公共前缀就行,数量就是1,不是2)
所以我们先用字典树存下来所以的字符串,逆向遍历,当遇到分叉节点时,答案加一,但是如果该分叉路径都已经算过了,那最后一个路径可以用一个前缀表示所有分叉的前缀。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<map>
#include<set>
#define first fi
#define second se
#define ios ios::sync_with_stdio(false); cin.tie(0);cout.tie(0);
using namespace std;
typedef long long ll;
const int N=3e6+10,M=5e4+10;
int n;
char s[M][55]; // 存入字符串
int son[N][30],fa[N],idx; // 记录字典树的儿子节点,父节点 ,idx是节点
int ans[N],ans1[N]; // 该节点有几个儿子
int fan[N]; // 记录每个字符串最后一位节点
int res; // 全局答案
int insert(char str[]) // 字典树插入
{
int p=0;
for(int i=0;str[i];i++)
{
int u=str[i]-'a';
if(str[i]=='.') u=28;
if(str[i]=='/') u=29;
if(!son[p][u])
{
son[p][u]=++idx;
fa[idx]=p;
++ans[p];
}
p=son[p][u];
}
ans[p]++; // 末端节点也存为1
return p;
}
void query(int x) // 字典树逆向查询
{
while(x)
{
ans1[x]++;
if(ans1[x]==ans[x])//如果凑齐该节点的所有儿子,可以继续向上走,而且可以用一个前缀表示该节点所有儿子的前缀
{
res-=(ans1[x]-1); // 减去该节点其他所有儿子 的前缀数量,用当前的一个就可以替代
x=fa[x];
}else return ; // 当没凑齐,就不能继续向上走。
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>s[i]+1;
fan[i]=insert(s[i]+1); // 记录末端节点
}
for(int i=1;i<=n;i++)
{
res++; // 当计算一个字符串,前缀加一
query(fan[i]); // 减去重复的前缀数量
cout<<res<<endl;
}
return 0;
}