题目描述
传送门
题目大意:有一棵n个节点的大树,上面每条边有一个小写字符。
对于任意两个不同的点u,v,我们可以在树上找到u出发到v终止的唯一的一条最短路径,并将沿途经过的边上的字符依次写下来,得到一个字符串。
对于一个字符串,如果存在这样一个点对(u,v),使得它们路径上的字符串与其完全匹配,那么我们就称这个字符串属于这棵树。
现在有m个迷失的字符串,请你写一个程序帮助判断每一条字符串是否属于这棵树。
题解
对拍不出错也卡不住,交上去不知道为什么RE。。。。欢迎指正啊
说一下思路吧。
f[i][j]
表示从下往上走到达第i个点匹配到了字符串中第j个字符是否可行。
可以将所有的串连起来一起做,中间加一个分隔符,初始时所有点只有分隔符位置是1.
考虑
f[i][j]
可行的条件
(1)他的儿子中有
f[son][j−1]
=1
(2)son->i边上的字符与j相同
这两个条件都可以用位运算实现。
判断一个串是否出现过,只需要判断同一个点的上行和下行路径能否拼接行成即可。
我们用两个数组,一个从后向前推j+1->j,一个从前向后推j-1->j,如果最后j,j+1同为1,那么说明可以拼接。
用bitset加速即可
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<bitset>
#define N 30100
using namespace std;
bitset<N*2> f[N],g[N],tmpf,tmpg,base[30],ans,t;
int n,m,tot,point[N],v[N*2],c[N*2],nxt[N*2],l[N],r[N];
char s[N];
void add(int x,int y,int k)
{
tot++; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; c[tot]=k;
tot++; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; c[tot]=k;
}
void dfs(int x,int fa)
{
bool pd=false;
for (int i=point[x];i;i=nxt[i]) {
if(v[i]==fa) continue;
dfs(v[i],x);
pd=true;
tmpf=(f[v[i]]<<1)&base[c[i]];
tmpg=(g[v[i]]>>1)&base[c[i]];
ans|=(f[x]<<1)&tmpg;
ans|=tmpf&(g[x]>>1);
f[x]|=tmpf; g[x]|=tmpg;
}
}
int main()
{
freopen("a.in","r",stdin);
freopen("my.out","w",stdout);
scanf("%d",&n);
for (int i=1;i<n;i++) {
int x,y; char s1[10];
scanf("%d%d%s",&x,&y,s1);
add(x,y,s1[0]-'a'+1);
}
scanf("%d",&m);
r[0]=1;
for (int i=1;i<=m;i++) {
scanf("%s",s+1);
int len=strlen(s+1);
l[i]=r[i-1]+1; r[i]=r[i-1]+len;
for (int j=1;j<=len;j++){
int t=s[j]-'a'+1;
base[t][j+r[i-1]]=1;
}
r[i]++;
}
for (int i=0;i<=m;i++) f[0][r[i]]=1; g[0]=f[0];
for (int i=1;i<=n;i++) f[i]=g[i]=f[0];
//for (int i=1;i<=r[m];i++) cout<<f[0][i]<<" "; cout<<endl;
dfs(1,0);
for (int i=1;i<=m;i++) {
bool pd=false;
for (int j=l[i];j<r[i];j++) pd|=ans[j];
if (pd) printf("YES\n");
else printf("NO\n");
}
}