1. 程式人生 > 其它 >Wannafly挑戰賽10F-小H和遺蹟【Trie,樹狀陣列】

Wannafly挑戰賽10F-小H和遺蹟【Trie,樹狀陣列】

正題

題目連結:https://ac.nowcoder.com/acm/contest/72/F


題目大意

\(n\)個字串,包括小寫字母和\(\#\)。其中\(\#\)可以替換為任意字串。求有多少對字串可能相同。

保證每個字串至少有一個\(\#\)

\(2\leq n\leq 500000,\sum_{i=1}^n |s_i|\leq 10^6\)


解題思路

因為可以替換為任意字串,所以只需要考慮第一個\(\#\)前和最後一個\(\#\)後的部分。

在仔細考慮一下,這個字串分成前後的兩個部分\(s,t\)。數對\((x,y)\)滿足條件當且僅當\(s_x\)\(s_y\)的字首,或者\(s_y\)\(s_x\)

的字首,且\(t_x\)\(t_y\)的字尾,或者\(t_y\)\(t_x\)的字尾。

放到兩棵\(Trie\)樹上就是都有祖先關係就好了,直接跑第一棵上,然後用兩個樹狀陣列在第二棵樹上維護就好了。

時間複雜度\(O(m\log m)\)


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define lowbit(x) (x&-x)
using namespace std;
const int N=1e6+10;
int n,cnt,pos[N],dfn[N],ed[N];
long long ans;char s[N];
vector<int> G[N]; 
struct TreeBinary{
	int t[N];
	void Change(int x,int val){
		while(x<=cnt){
			t[x]+=val;
			x+=lowbit(x);
		}
		return;
	}
	int Ask(int x){
		int ans=0;
		while(x){
			ans+=t[x];
			x-=lowbit(x);
		}
		return ans;
	}
}Bf,Bs;
struct Trie{
	int t[N][26],m=1;
	int Insert(char *s,int n){
		int x=1;
		for(int i=1;i<=n;i++){
			if(s[i]=='#')break;
			int c=s[i]-'a';
			if(!t[x][c])t[x][c]=++m;
			x=t[x][c];
		}
		return x;
	}
}Tp,Ts;
void dfs(int x){
	dfn[x]=++cnt;
	for(int i=0;i<26;i++)
		if(Ts.t[x][i])
			dfs(Ts.t[x][i]);
	ed[x]=cnt;
}
void work(int x){
	for(int i=0;i<G[x].size();i++){
		int p=G[x][i];ans+=Bf.Ask(dfn[pos[p]]);
		Bf.Change(dfn[pos[p]],1);Bf.Change(ed[pos[p]]+1,-1);
		ans+=Bs.Ask(ed[pos[p]])-Bs.Ask(dfn[pos[p]]); 
		Bs.Change(dfn[pos[p]],1);
	}
	for(int i=0;i<26;i++)
		if(Tp.t[x][i])
			work(Tp.t[x][i]);
	for(int i=0;i<G[x].size();i++){
		int p=G[x][i];Bs.Change(dfn[pos[p]],-1);
		Bf.Change(dfn[pos[p]],-1);Bf.Change(ed[pos[p]]+1,1);
	}
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%s",s+1);
		int l=strlen(s+1);
		int x=Tp.Insert(s,l);
		G[x].push_back(i);
		reverse(s+1,s+1+l);
		pos[i]=Ts.Insert(s,l);
	}
	dfs(1);
	work(1);
	printf("%lld\n",ans);
	return 0;
}