目录:
题目:
分析:
首先是读入。这道题全部采用
2
2
进制,也就是说只有和
1
1
两个字符/数字,那么我们直接把它们当成字符用好了。
然后建树,也是一般的字典树建树法,只是把字母换成了和
1
1
而已,这里不多讲。
一边建树,一边在这串数列中走过的路径中的,
sum
s
u
m
代表有
sum
s
u
m
个单词经过这个节点。
当一个单词插完以后,在当前节点(即该单词的最后一个数字)的
end+1
e
n
d
+
1
,
end
e
n
d
代表有
end
e
n
d
个单词在这个节点终结。
注意可能会有重复的信息,也就是说可能会有多条信息在同一个节点终结,所以这里的
end
e
n
d
是数字而不是布尔型。
然后读入待查询的信息,就像普通字典树查询一样地往下走,一边走一边把沿途的
end
e
n
d
值加起来。
循环结束,有两种情况:一是该信息全部走完,二是再往下走没有与该信息相符的节点了。
第一种情况,当前的答案要减去当前节点的
end
e
n
d
值再加上当前节点的
sum
s
u
m
值(想一想,为什么)。
第二种情况,直接输出答案。
显然比待查询的信息长的信息,如果与待查询信息有相同前缀的话,一定会经过待查询信息终结的节点。
如果待查询信息无法终结,说明没有比该信息长且前缀是该信息的信息,所以不能加上
sum
s
u
m
。
如果信息终结,那么当前节点的
sum
s
u
m
值所包含的信息一定与其有相同前缀,但
sum
s
u
m
所包含的信息有可能刚好在该节点终结,所以要减去
end
e
n
d
值。
代码:
// luogu-judger-enable-o2
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<set>
#include<map>
#include<list>
#include<ctime>
#include<iomanip>
#include<string>
#include<bitset>
#define LL long long
using namespace std;
inline LL read() {
LL d=0,f=1;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){d=d*10+s-'0';s=getchar();}
return d*f;
}
struct node{
int end,cnt;
int son[2];
node()
{
son[1]=son[0]=0;
end=cnt=0;
}
}trie[500001];
int num=0;
char s[10001];
int a;
void in(char *s)
{
int w;
int f=0;
for(int i=0;i<a;i++)
{
w=s[i]-'0';
if(!trie[f].son[w])
trie[f].son[w]=++num;
f=trie[f].son[w];
trie[f].cnt++;
}
trie[f].end++;
}
int ans=0;
void ask(char *s)
{
int tf=0;
int f=0;
int w;
for(int i=0;i<a;i++)
{
w=s[i]-'0';
if(trie[f].son[w]==0)
{
tf=1;
break;
}
f=trie[f].son[w];
ans+=trie[f].end;
}
if(tf==0) ans=ans-trie[f].end+trie[f].cnt;
}
int main()
{
int n=read(),m=read();
for(int i=1;i<=n;i++)
{
a=read();
for(int j=1;j<=a;j++)
{
int b=read();
s[j-1]=char(b+48);
}
in(s);
}
for(int i=1;i<=m;i++)
{
a=read();
for(int j=1;j<=a;j++)
{
int b=read();
s[j-1]=char(b+48);
}
ans=0;
ask(s);
printf("%d\n",ans);
}
return 0;
}