1. 程式人生 > 其它 >[省選集訓2022] 模擬賽17

[省選集訓2022] 模擬賽17

選拔

題目描述

給定一棵邊帶字元的樹,有 \(m\) 次詢問,每次問一個字串是否對應著樹上的一條簡單路徑。

\(n,m\leq 30000\),詢問字串總長不超過 \(30000\)

解法

考慮詢問串出現在樹上的形式一定是從下到上的路徑和從上到下的路徑拼接起來。

考慮 \(dp\),設 \(f(u,i,j)\) 表示從下到上到 \(u\),是否可以匹配到字串 \(i\) 的字首 \(j\)\(g(u,i,j)\) 表示 \(u\) 開始從上到下,是否可以匹配到字串 \(i\) 的字尾 \(j\)

兩者都可以很容易地從子樹轉移上來,發現詢問串的總長很小,所以我們把詢問串拼在一起作為狀態,在中間插入分隔符,那麼就可以用 \(\tt bitset\)

優化轉移了,時間複雜度 \(O(\frac{nm}{w})\)

注意轉移不能直接 \(\tt dfs\) 做,要先把尤拉序搞出來迴圈做,要不然會爆記憶體(可能是計算機原理,具體原因不清楚)

#include <cstdio>
#include <vector>
#include <bitset>
#include <cstring>
#include <iostream> 
using namespace std;
const int M = 30005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,k,fa[M],fc[M],id[M],l[M],r[M];
bitset<M<<1> f[M],g[M],pos[27],ans;char s[M],t[M<<1];
struct edge{int v,c;};vector<edge> G[M];
void dfs(int u,int p)
{
	fa[u]=p;id[++k]=u;
	for(auto x:G[u]) if(x.v^p)
		fc[x.v]=x.c,dfs(x.v,u);
}
signed main()
{
	freopen("selection.in","r",stdin);
	freopen("selection.out","w",stdout);
	n=read();
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read();scanf("%s",s);
		G[u].push_back({v,s[0]-'a'});
		G[v].push_back({u,s[0]-'a'});
	}
	dfs(1,0);
	m=read();k=0;
	for(int i=1;i<=m;i++)
	{
		scanf("%s",s);
		int len=strlen(s);
		l[i]=k;t[k++]='#';
		for(int j=0;j<len;j++) t[k++]=s[j];
		t[r[i]=k]='#';
	}
	for(int i=0;i<=k;i++)
	{
		if(t[i]!='#') pos[t[i]-'a'].set(i);
		else pos[26].set(i);
	}
	for(int i=1;i<=n;i++) f[i]=g[i]=pos[26];
	for(int i=n;i>=1;i--)
	{
		int u=id[i],v=fa[u],x=fc[u];
		if(!v) continue;
		ans|=(f[u]<<1)&pos[x]&(g[v]>>1);
		ans|=(f[v]<<1)&pos[x]&(g[u]>>1);
		f[v]|=pos[x]&(f[u]<<1);
		g[v]|=pos[x]&(g[u]>>1);
	}
	for(int i=1;i<=m;i++)
	{
		int fl=0;
		for(int j=l[i];j<r[i];j++) fl|=ans[j];
		puts(fl?"YES":"NO");
	}
}