标签: 倍增

[NOIP2012提高]开车旅行 题解

[NOIP2012提高]开车旅行 题解

题目地址:洛谷:【P1081】开车旅行 – 洛谷

题目描述

小 A 和小 B 决定利用假期外出旅行,他们将想去的城市从 1 到 N 编号,且编号较小的城市在编号较大的城市的西边,已知各个城市的海拔高度互不相同,记城市 i 的海拔高度为Hi,城市 i 和城市 j 之间的距离 d[i,j]恰好是这两个城市海拔高度之差的绝对值,即d[i,j] = |Hi− Hj|。 旅行过程中,小 A 和小 B 轮流开车,第一天小 A 开车,之后每天轮换一次。他们计划选择一个城市 S 作为起点,一直向东行驶,并且最多行驶 X 公里就结束旅行。小 A 和小 B的驾驶风格不同,小 B 总是沿着前进方向选择一个最近的城市作为目的地,而小 A 总是沿着前进方向选择第二近的城市作为目的地(注意:本题中如果当前城市到两个城市的距离相同,则认为离海拔低的那个城市更近)。如果其中任何一人无法按照自己的原则选择目的城市,或者到达目的地会使行驶的总距离超出 X 公里,他们就会结束旅行。
在启程之前,小 A 想知道两个问题:

  1. 对于一个给定的 X=X0,从哪一个城市出发,小 A 开车行驶的路程总数与小 B 行驶的路程总数的比值最小(如果小 B 的行驶路程为 0,此时的比值可视为无穷大,且两个无穷大视为相等)。如果从多个城市出发,小 A 开车行驶的路程总数与小 B 行驶的路程总数的比值都最小,则输出海拔最高的那个城市。
  2. 对任意给定的 X=Xi和出发城市 Si,小 A 开车行驶的路程总数以及小 B 行驶的路程总数。

输入输出格式

输入格式:
第一行包含一个整数 N,表示城市的数目。
第二行有 N 个整数,每两个整数之间用一个空格隔开,依次表示城市 1 到城市 N 的海拔高度,即 H1,H2,……,Hn,且每个 Hi都是不同的。
第三行包含一个整数 X0。
第四行为一个整数 M,表示给定 M 组 Si和 Xi。
接下来的 M 行,每行包含 2 个整数 Si和 Xi,表示从城市 Si出发,最多行驶 Xi公里。

输出格式:
输出共 M+1 行。
第一行包含一个整数 S0,表示对于给定的 X0,从编号为 S0的城市出发,小 A 开车行驶的路程总数与小 B 行驶的路程总数的比值最小。
接下来的 M 行,每行包含 2 个整数,之间用一个空格隔开,依次表示在给定的 Si和
Xi下小 A 行驶的里程总数和小 B 行驶的里程总数。

输入输出样例

输入样例#1:

4 
2 3 1 4 
3 
4 
1 3 
2 3 
3 3 
4 3

输出样例#1:

1 
1 1 
2 0 
0 0 
0 0 

输入样例#2:

10 
4 5 6 1 2 3 7 8 9 10 
7 
10 
1 7 
2 7 
3 7 
4 7 
5 7 
6 7 
7 7 
8 7 
9 7 
10 7

输出样例#2:

2 
3 2 
2 4 
2 1 
2 4 
5 1 
5 1 
2 1 
2 0 
0 0 
0 0

说明

对于30%的数据,有1≤N≤20,1≤M≤20;
对于40%的数据,有1≤N≤100,1≤M≤100;
对于50%的数据,有1≤N≤100,1≤M≤1,000;
对于70%的数据,有1≤N≤1,000,1≤M≤10,000;
对于100%的数据,有1≤N≤100,000,1≤M≤100,000,-1,000,000,000≤Hi≤1,000,000,000,0≤X0≤1,000,000,000,1≤Si≤N,0≤Xi≤1,000,000,000,数据保证Hi 互不相同。

题解

我们可以把距离加入一个set中,每次从set中二分找到当前处理的城市,从该城市的前驱,前驱的前驱,后继,后继的后继中找到两个最近的作为A和B的目标城市,组成一个链表,处理完一个城市就从set中删掉一个城市。然后在链表上二分,处理出nnxt[i][j]表示从i出发,两人各开了[eq]2^j[/eq]天后到达的城市,顺便求出disa[i][j]表示从i出发,两人各开了[eq]2^j[/eq]天,这段时间中A行驶的距离,以及disb[i][j]类似前面的定义。这个倍增列表可以通过常规的类似倍增LCA的方法求到。
对于第一个问题,枚举出发地利用上述预处理信息计算比值即可。
对于第二个问题,直接计算即可。
注意倍增终点最后一次是B开的,也就是说A可能还会继续往前开一次,这个要特判一下。
其实是个码农细节题,写起来挺烦的。思路上难度不太大。
复杂度上,预处理链表是O(N \log N),预处理倍增数组是O(N \log N),子问题1是O(N \log N),子问题2是O(M \log N)。因此总复杂度还是O(N \log N)

代码

// Code by KSkun, 2018/5
#include <cstdio>
#include <cctype>

#include <algorithm>
#include <set>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF 
        : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    register char c = fgc();
    while(!isdigit(c)) {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(isdigit(c)) {
        res = (res << 1) + (res << 3) + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 100005, INF = 1e9;
const double EPS = 1e-8;

struct Node {
    int hei, id;
    inline bool operator<(const Node &rhs) const {
        return hei < rhs.hei;
    }
};

std::set<Node> heis;
int n, hei[MAXN], nxt[MAXN][2], nnxt[MAXN][20], disa[MAXN][20], disb[MAXN][20], x0, m, si, xi;

int main() {
    n = readint();
    for(int i = 1; i <= n; i++) {
        hei[i] = readint();
        heis.insert(Node {hei[i], i});
    }
    for(int i = 1; i <= n; i++) {
        auto it1 = heis.lower_bound(Node {hei[i], i}), it2 = it1;
        if(it1 != heis.end()) it1++; 
        if(it2 != heis.begin()) it2--;
        for(int j = 0; j < 2; it1++, j++) {
            if(it1 == heis.end()) break;
            if(!nxt[i][0]) {
                nxt[i][0] = it1->id;
            } else if(abs(hei[i] - it1->hei) < abs(hei[i] - hei[nxt[i][0]])
                || (abs(hei[i] - it1->hei) == abs(hei[i] - hei[nxt[i][0]]) 
                && it1->hei < hei[nxt[i][0]])) {
                nxt[i][1] = nxt[i][0];
                nxt[i][0] = it1->id;
            } else if(!nxt[i][1]) {
                nxt[i][1] = it1->id;
            } else if(abs(hei[i] - it1->hei) < abs(hei[i] - hei[nxt[i][1]])
                || (abs(hei[i] - it1->hei) == abs(hei[i] - hei[nxt[i][1]]) 
                && it1->hei < hei[nxt[i][1]])) {
                nxt[i][1] = it1->id;
            }
        }
        for(int j = 0; j < 2; it2--, j++) {
            if(it2->id == i && it2 == heis.begin()) break;
            if(!nxt[i][0]) {
                nxt[i][0] = it2->id;
            } else if(abs(hei[i] - it2->hei) < abs(hei[i] - hei[nxt[i][0]])
                || (abs(hei[i] - it2->hei) == abs(hei[i] - hei[nxt[i][0]]) 
                && it2->hei < hei[nxt[i][0]])) {
                nxt[i][1] = nxt[i][0];
                nxt[i][0] = it2->id;
            } else if(!nxt[i][1]) {
                nxt[i][1] = it2->id;
            } else if(abs(hei[i] - it2->hei) < abs(hei[i] - hei[nxt[i][1]])
                || (abs(hei[i] - it2->hei) == abs(hei[i] - hei[nxt[i][1]]) 
                && it2->hei < hei[nxt[i][1]])) {
                nxt[i][1] = it2->id;
            }
            if(it2 == heis.begin()) break;
        }
        heis.erase(Node {hei[i], i});
    }
    for(int i = 1; i <= n; i++) {
        nnxt[i][0] = nxt[nxt[i][1]][0];
        disa[i][0] = abs(hei[nxt[i][1]] - hei[i]);
        disb[i][0] = abs(hei[nnxt[i][0]] - hei[nxt[i][1]]);
    }
    for(int k = 1; k < 20; k++) {
        for(int i = 1; i <= n; i++) {
            nnxt[i][k] = nnxt[nnxt[i][k - 1]][k - 1];
            disa[i][k] = disa[i][k - 1] + disa[nnxt[i][k - 1]][k - 1];
            disb[i][k] = disb[i][k - 1] + disb[nnxt[i][k - 1]][k - 1];
        }
    }
    x0 = readint();
    double mn = INF * 2; int mnx = 0;
    for(int i = 1; i <= n; i++) {
        int da = 0, db = 0, p = i, d = 0;
        for(int k = 19; k >= 0; k--) {
            if(nnxt[p][k] && d + disa[p][k] + disb[p][k] <= x0) {
                d += disa[p][k] + disb[p][k];
                da += disa[p][k];
                db += disb[p][k];
                p = nnxt[p][k];
            }
        }
        if(nxt[p][1] && d + abs(hei[nxt[p][1]] - hei[p]) <= x0) {
            d += abs(hei[nxt[p][1]] - hei[p]);
            da += abs(hei[nxt[p][1]] - hei[p]);
            p = nxt[p][1];
        }
        double now = double(da) / db;
        if(db == 0) {
            if(mn - INF >= EPS) {
                mn = INF;
                mnx = i;
            } else if(fabs(mn - INF) < EPS && hei[i] > hei[mnx]) {
                mnx = i;
            }
        } else {
            if(mn - now >= EPS) {
                mn = now;
                mnx = i;
            } else if(fabs(mn - now) < EPS && hei[i] > hei[mnx]) {
                mnx = i;
            }
        }
    }
    printf("%d\n", mnx);
    m = readint();
    while(m--) {
        si = readint(); xi = readint();
        int p = si, d = 0, da = 0, db = 0;
        for(int k = 19; k >= 0; k--) {
            if(nnxt[p][k] && d + disa[p][k] + disb[p][k] <= xi) {
                d += disa[p][k] + disb[p][k];
                da += disa[p][k];
                db += disb[p][k];
                p = nnxt[p][k];
            }
        }
        if(nxt[p][1] && d + abs(hei[nxt[p][1]] - hei[p]) <= xi) {
            d += abs(hei[nxt[p][1]] - hei[p]);
            da += abs(hei[nxt[p][1]] - hei[p]);
            p = nxt[p][1];
        }
        printf("%d %d\n", da, db);
    }
    return 0;
}
[SCOI2012]喵星球上的点名 题解

[SCOI2012]喵星球上的点名 题解

题目地址:洛谷:【P2336】[SCOI2012]喵星球上的点名 – 洛谷、BZOJ:Problem 2754. — [SCOI2012]喵星球上的点名

题目描述

a180285 幸运地被选做了地球到喵星球的留学生。他发现喵星人在上课前的点名现象非常有趣。
假设课堂上有 N 个喵星人,每个喵星人的名字由姓和名构成。喵星球上的老师会选择M 个串来点名,每次读出一个串的时候,如果这个串是一个喵星人的姓或名的子串,那么这个喵星人就必须答到。
然而,由于喵星人的字码过于古怪,以至于不能用 ASCII 码来表示。为了方便描述,a180285 决定用数串来表示喵星人的名字。
现在你能帮助 a180285 统计每次点名的时候有多少喵星人答到,以及 M 次点名结束后每个喵星人答到多少次吗?

输入输出格式

输入格式:
现在定义喵星球上的字符串给定方法:
先给出一个正整数 L ,表示字符串的长度,接下来L个整数表示字符串的每个字符。
输入的第一行是两个整数 N 和 M。
接下来有 N 行, 每行包含第 i 个喵星人的姓和名两个串。 姓和名都是标准的喵星球上的字符串。
接下来有 M 行,每行包含一个喵星球上的字符串,表示老师点名的串。

输出格式:
对于每个老师点名的串输出有多少个喵星人应该答到。
然后在最后一行输出每个喵星人被点到多少次。

输入输出样例

输入样例#1:

2 3
6 8 25 0 24 14 8 6 18 0 10 20 24 0
7 14 17 8 7 0 17 0 5 8 25 0 24 0
4 8 25 0 24
4 7 0 17 0
4 17 0 8 25

输出样例#1:

2
1
0
1 2

说明

对于30%的数据,保证:
1 ≤ N ,M ≤ 1000 喵星人的名字总长不超过 4000,点名串的总长不超过 2000,
对于100%的数据,保证:
1 ≤ N ≤ 20000, 1 ≤ M ≤ 50000 喵星人的名字总长和点名串的总长分别不超过100000
保证喵星人的字符串中作为字符存在的数不超过10000

题解

AC自动机暴力

考虑一个AC自动机的暴力做法。首先因为字符集大小并不确定,而且可能很大,我们要考虑map存儿子。接着因为字符集大小不确定我们没办法加虚边了只能暴力跳fail。这导致了最后洛谷#11 TLE的惨案。
我们考虑把点名串扔进去,把姓名用一个非法字符连接起来来匹配。每匹配到一个点名串在点名串的计数器上+1,然后统计这个姓名匹配到了多少点名串。
记录该点是否被访问过的数组vis开的很大,最好不要memset,用一个栈记下修改过的节点最后改回去。
我加了一堆register,全部inline,能避免STL的改手写,加了fread,然而还是敌不过洛谷的#11 QAQ!
所以说这是个AC自动机暴力啦,我也不会fail树高论,也不会SA,就这样吧,以后学了SA再来A这题。
对不起,SA真的是能为所欲为的!

fail树优化

如果我们将AC自动机中所有fail指针(未路径压缩)反向建有向边,会得到一棵以0为根方向向叶子节点的有向树。利用fail树的有关性质,我们可以把字符串匹配问题转换成树上问题,优化复杂度。本题中也可以使用该种算法。
首先我们可以类似的在姓名中间加一个不会出现的字符,这里我用的是-1,插入Trie树中,然后计算fail指针建立fail树。需要注意的是fail指针无法路径压缩,因此只能暴力跳fail来找。为了方便,后面建树的时候对节点编号+1处理了一下,避免0的特判。事实证明,这一操作为后面的处理带来了不少的麻烦,许多地方忘记+1换算了。有了fail树以后,我们要利用fail树的相关性质解决题目问题。
对于问题1,是求一个模式串出现在哪些文本串中了,这个问题可以转化为一个模式串的末尾点在fail树的子树中有多少不同文本串的节点。这是由于,文本串中出现过的模式串位置末尾的fail指针会指向这一模式串的末尾,由于反向,就变成了模式串末尾指向文本串节点了,那就是一个子树问题。这个问题可以转化成一个DFS序序列上的数颜色种数问题,这让我们想起了[SDOI2009]HH的项链一题,采用相同的树状数组+扫描线的做法即可求出。因此,我们需要标注每个节点属于哪个文本串这样一个颜色信息,并且处理出一个每个颜色有哪些点的链表,方便扫描线维护信息。如果文本串很多,我们不可能每次都枚举一下每个颜色,因此不如再开个链表记录一下每个点上有几种颜色。链表空间紧凑,适合用来做这种不确定空间且不需要随机查找的统计问题。另外还要处理出子树大小,方便确定查询区间。上述信息处理完毕以后,就是上面那个省选题的做法了。这一部分的复杂度是O(n \log n)的。
对于问题2,求一个文本串中出现过几种模式串。如果我们默认每个节点初始权值为0,而对每个模式串结尾节点的权值加1,则模式串到树根路径上的点权和就是这个点为结尾的后缀为模式串的个数,如果将文本串上每个节点到树根的路径求并求权值和,就是我们要的答案了。树链的并我们可以用树链剖分+线段树区间染色求和的方式来求,这样做则是O(n \log^2 n)的。如果想只有一个log,可以用将节点按照DFS序排序后的顺序统计信息,加入第i个节点的时候,减去i与i-1节点的LCA到树根的路径权值和,这样可以避免这一段公共路径权值和被计入两次,起到了去重的效果。求LCA采用倍增法,则复杂度可以降为O(n \log n)了。
但是我们仔细想一下,这些内容都很长。我们需要Trie树的插入和统计信息,需要两个链表分别维护一个节点上的颜色种类和一个颜色涉及的节点数,需要一个树状数组,需要一个树DFS统计树信息,需要一个倍增LCA的处理,需要一个离线处理区间颜色数的算法,需要一个按DFS序排序去重的计数算法。以上就是写出这道题的这种解法的所有需求,接下来就是一步一步地去实现它们了。
在实现过程中,遇到了种种问题,最困惑我的一点就是写代码的时候DFS序号和节点序号搞混了,以及节点序号+1忘记了,不过如果能够自己实现一遍,相信是很锻炼代码能力和调试能力的。这个题今天花了一天来做,收获也很大。

代码

AC自动化暴力

// Code by KSkun, 2018/3
#include <cstdio>
#include <cstring>

#include <map>
#include <vector>

struct io {
    char buf[1 << 26], *s;

    io() {
        fread(s = buf, 1, 1 << 26, stdin);
    }

    inline int read() {
        register int res = 0;
        while(*s < '0' || *s > '9') s++;
        while(*s >= '0' && *s <= '9') res = res * 10 + *s++ - '0';
        return res;
    }
} ip;

#define readint ip.read

const int MAXN = 100005, MAXL = 100005;

int n, m;
std::vector<int> names[MAXN], val[MAXL];
int fail[MAXL], cnt;
std::map<int, int> ch[MAXL];
int que[MAXL], l, r;

inline void insert(int id) {
    register int len = readint(), p = 0;
    for(register int i = 0; i < len; i++) {
        int t = readint();
        if(!ch[p][t]) ch[p][t] = ++cnt;
        p = ch[p][t];
    }
    val[p].push_back(id);
}

inline void calfail() {
    l = r = 0;
    fail[0] = 0;
    for(std::map<int, int>::iterator it = ch[0].begin(); it != ch[0].end(); it++) {
        register int u = it->second;
        que[r++] = u;
        fail[u] = 0;
    }
    while(l != r) {
        register int u = que[l++];
        for(std::map<int, int>::iterator it = ch[u].begin(); it != ch[u].end(); it++) {
            int t = it->first, v = it->second, p = fail[u];
            while(p && !ch[p][t]) p = fail[p];
            fail[v] = ch[p][t];
            que[r++] = v;
        }
    }
}

int ctn[MAXN], qur[MAXN];
bool vis[MAXL];
int sta[MAXL], stot;

inline int calcnt(int p) {
    register int res = 0;
    for(; p; p = fail[p]) {
        if(vis[p]) break;
        vis[p] = true;
        sta[++stot] = p;
        for(register int i = 0; i < val[p].size(); i++) {
            res++;
            qur[val[p][i]]++;
        }
    }
    return res;
}

inline void query(int id) {
    register int p = 0;
    stot = 0;
    for(register int i = 0; i < names[id].size(); i++) {
        int t = names[id][i];
        while(p && !ch[p][t]) p = fail[p];
        p = ch[p][t];
        ctn[id] += calcnt(p);
    }
    for(register int i = 1; i <= stot; i++) {
        vis[sta[i]] = false;
    }
}

int main() {
    n = readint(); m = readint();
    for(register int i = 1; i <= n; i++) {
        register int len = readint();
        while(len--) names[i].push_back(readint());
        names[i].push_back(-1);
        len = readint();
        while(len--) names[i].push_back(readint());
    }
    for(register int i = 1; i <= m; i++) {
        insert(i);
    }
    calfail();
    for(register int i = 1; i <= n; i++) {
        query(i);
    }
    for(register int i = 1; i <= m; i++) {
        printf("%d\n", qur[i]);
    }
    for(register int i = 1; i <= n; i++) {
        printf("%d ", ctn[i]);
    }
    return 0;
}

fail树优化(附注释)

// Code by KSkun, 2018/5
#include <cstdio>
#include <cctype>

#include <algorithm>
#include <map>
#include <queue>
#include <vector>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) 
        ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    register char c = fgc();
    while(!isdigit(c)) {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(isdigit(c)) {
        res = (res << 1) + (res << 3) + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 200005;

// 双向链表 
struct LinkedList {
    int head[MAXN], tail[MAXN], val[MAXN], pre[MAXN], nxt[MAXN], tot;
    LinkedList() {
        tot = 0;
    }
    // 在p所属的链表头部插入v这个值 
    inline void insert(int p, int v) {
        tot++; 
        if(!tail[p]) tail[p] = tot;
        val[tot] = v; if(head[p]) pre[head[p]] = tot; nxt[tot] = head[p]; head[p] = tot;
    }
} col, col2;

// 树状数组 
struct BinaryIndexTree {
    int tr[MAXN];
    inline int lowbit(int x) {
        return x & -x;
    }
    inline void add(int x, int v) {
        for(int i = x; i < MAXN; i += lowbit(i)) {
            tr[i] += v;
        }
    }
    inline int query(int x) {
        int res = 0;
        for(int i = x; i; i -= lowbit(i)) {
            res += tr[i];
        }
        return res;
    }
} bit;

// 字符集太大,用map存儿子省空间 
std::map<int, int> ch[MAXN];
std::queue<int> que;
std::vector<int> gra[MAXN]; // fail树存在这里 
int fail[MAXN], ecnt[MAXN], end[MAXN], tot;

// Trie树的插入,分别处理文本串和模式串 
inline void insert(std::vector<int> &str, int id, bool isname) {
    int p = 0;
    for(int i = 0; i < str.size(); i++) {
        int t = str[i];
        if(!ch[p][t]) {
            ch[p][t] = ++tot;
        }
        if(isname) {
            // 统计每个节点上的颜色种类 
            col.insert(ch[p][t] + 1, id);
        }
        p = ch[p][t];
    }
    if(!isname) {
        // 统计一个节点是多少模式串的结尾,模式串可能有多个相同不可用bool 
        ecnt[p + 1]++; end[id] = p + 1;
    }
}

// 计算fail数组 
inline void calfail() {
    for(std::map<int, int>::iterator it = ch[0].begin(); it != ch[0].end(); it++) {
        que.push(it->second);
        gra[1].push_back(it->second + 1);
    }
    while(!que.empty()) {
        int p = que.front(); que.pop();
        for(std::map<int, int>::iterator it = ch[p].begin(); it != ch[p].end(); it++) {
            int t = fail[p], q = it->second;
            // 暴力跳fail计算 
            while(!ch[t][it->first] && t) t = fail[t];
            fail[q] = ch[t][it->first];
            gra[ch[t][it->first] + 1].push_back(q + 1);
            que.push(q);
        }
    }
}

int anc[MAXN][20], siz[MAXN], dfn[MAXN], dep[MAXN], ptn[MAXN], sum[MAXN], clk;

// DFS处理fail树信息 
inline void dfs(int u) {
    siz[u] = 1; dfn[u] = ++clk; ptn[dfn[u]] = u;
    sum[u] = sum[anc[u][0]] + ecnt[u];
    for(int i = 0; i < gra[u].size(); i++) {
        int v = gra[u][i];
        if(v == anc[u][0]) continue;
        dep[v] = dep[u] + 1;
        anc[v][0] = u;
        dfs(v);
        siz[u] += siz[v];
    }
}

int n, m;

struct Query {
    int id, l, r;
    inline bool operator<(const Query &rhs) const {
        return l == rhs.l ? r < rhs.r : l < rhs.l;
    }
} qur[MAXN];

// 第一个问题

int ans1[MAXN];

inline void main1() {
    // 按DFS序处理出每个颜色的链表 
    for(int i = 1; i < MAXN; i++) {
        for(int j = col.head[ptn[i]]; j; j = col.nxt[j]) {
            int c = col.val[j];
            col2.insert(c, i);
        }
    }
    // 初始化询问以及对询问排序 
    for(int i = 1; i <= m; i++) {
        qur[i] = Query {i, dfn[end[i]], dfn[end[i]] + siz[end[i]] - 1};
    }
    // 初始化树状数组,在每个颜色的初位置+1 
    for(int i = 1; i <= n; i++) {
        bit.add(col2.val[col2.tail[i]], 1);
    }
    std::sort(qur + 1, qur + m + 1);
    int lst = 1;
    // 同[SDOI2009]HH的项链
    for(int i = 1; i <= m; i++) {
        if(lst != qur[i].l) {
            for(int j = lst; j < qur[i].l; j++) {
                for(int k = col.head[ptn[j]]; k; k = col.nxt[k]) {
                    int c = col.val[k];
                    bit.add(j, -1);
                    col2.tail[c] = col2.pre[col2.tail[c]];
                    if(col2.val[col2.tail[c]]) bit.add(col2.val[col2.tail[c]], 1);
                }
            }
            lst = qur[i].l;
        }
        ans1[qur[i].id] = bit.query(qur[i].r);
    }
    for(int i = 1; i <= m; i++) {
        printf("%d\n", ans1[i]);
    }
}

// 倍增LCA
inline void preparelca() {
    for(int j = 1; j < 20; j++) {
        for(int i = 1; i < MAXN; i++) {
            anc[i][j] = anc[anc[i][j - 1]][j - 1];
        }
    }
}

inline int querylca(int x, int y) {
    if(dep[x] > dep[y]) std::swap(x, y);
    int del = dep[y] - dep[x];
    for(int i = 19; i >= 0; i--) {
        if(del & (1 << i)) {
            y = anc[y][i];
        }
    }
    if(x == y) return x;
    for(int i = 19; i >= 0; i--) {
        if(anc[x][i] != anc[y][i]) {
            x = anc[x][i]; y = anc[y][i];
        }
    }
    return anc[x][0];
}

// 第二个问题 

std::vector<int> name[MAXN];
int ans2[MAXN];

inline bool cmp(int a, int b) {
    return dfn[a] < dfn[b];
}

inline void main2() {
    for(int i = 1; i <= n; i++) {
        int p = 0;
        // 将字符串换成该字符串的节点 
        for(int j = 0; j < name[i].size(); j++) {
            p = ch[p][name[i][j]];
            name[i][j] = p + 1;
        }
        // 按DFS序计算树链的并的权值和 
        std::sort(name[i].begin(), name[i].end(), cmp);
        for(int j = 0; j < name[i].size(); j++) {
            ans2[i] += sum[name[i][j]];
            if(j) ans2[i] -= sum[querylca(name[i][j], name[i][j - 1])];
        }
    }
    for(int i = 1; i <= n; i++) {
        printf("%d ", ans2[i]);
    }
}

int len;
std::vector<int> s;

int main() {
    n = readint(); m = readint();
    for(int i = 1; i <= n; i++) {
        s.clear();
        len = readint();
        while(len--) {
            s.push_back(readint());
        }
        s.push_back(-1);
        len = readint();
        while(len--) {
            s.push_back(readint());
        }
        insert(s, i, true);
        name[i] = s;
    }
    for(int i = 1; i <= m; i++) {
        s.clear();
        len = readint();
        while(len--) {
            s.push_back(readint());
        }
        insert(s, i, false);
    }
    calfail();
    dfs(1);
    preparelca();
    main1();
    main2();
    return 0;
}