首页 > 其他分享 >【题解】P4218 [CTSC2010]珠宝商

【题解】P4218 [CTSC2010]珠宝商

时间:2023-01-07 14:00:36浏览次数:42  
标签:sz cur sam int 题解 len fa CTSC2010 P4218

这种题出出来有什么必要吗,不就是难写的暴力弱智题。

题意

给定一棵树和一个文本串 \(T\),每个结点上有一个字符,问树上任意路径构成的字符串在 \(T\) 中的出现次数之和。

\(n, m \leq 5 \times 10^4\)

思路

点分治 + 后缀自动机 + 根号分治。

首先可以发现期望是假的。

然后考虑点分治做树上路径计数。这里对于当前的分治重心 \(u\),统计经过 \(u\) 的所有路径构成的字符串在 \(S\) 中的出现次数之和。

看到子串出现次数之和考虑用 SAM 做。

听起来很复杂对不对?暴力怎么做?搜索!搜索!搜索!

考虑把一条经过点 \(u\) 的树上路径 \(p \rightarrow q\) 分成 \(p \rightarrow u\) 和 \(u \rightarrow q\) 两部分,后面那一部分可以在子树里面直接搜,暴力在 parent tree 上跳。

第一部分是在反串上做的镜像问题,因为要在整串的前方加入一个字符,所以需要在 parent tree 上先处理出在整串的前方加入字符所导向的子结点,这个很好做。

在 parent tree 上跳到某个结点终止后,显然这个结点所代表的串的任意一个后缀都在 \(T\) 中出现过,所以出现次数是所有祖先的出现次数之和,考虑做一个树上前缀和。

考虑统计答案:考虑 \(T\) 中每一个位置的贡献,那么答案就是 \(\sum\limits\) 前缀 \([1, i]\) 的任意后缀在 \(T\) 中的出现次数之和 \(\times\) 后缀 \([i + 1, m]\) 的任意前缀在 \(T\) 中的出现次数之和。

这里的时间复杂度是 \(O(size + m)\),约等于白做。

但是我们还有另一种暴力:枚举子树中的初始结点,然后暴搜接下来的树上路径,复杂度 \(O(size^2)\)

那直接套一个根号分治就做完了。

代码

#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
using namespace std;

typedef long long ll;

const int maxn = 5e4 + 5;
const int sam_sz = 1e5 + 5;
const int inf = 0x3f3f3f3f;

int n, m;
int rt, sz_max, sz_sum, block;
int qlen, q[maxn], up[maxn], rc[maxn];
ll ans;
int sz[maxn];
bool vis[maxn];
char s[maxn], t[maxn];
vector<int> g[maxn];

struct SAM
{
    int cur = 1, lst = 1;
    int nd[maxn];
    int len[sam_sz], fa[sam_sz], son[sam_sz][26], to[sam_sz][26];
    int cnt[sam_sz], pos[sam_sz], seq[sam_sz], tot[sam_sz], f[sam_sz];
    char str[sam_sz];

    void clear() { memset(f, 0, (cur + 1) * sizeof(int)); }

    void copy_node(int to, int from)
    {
        len[to] = len[from], fa[to] = fa[from], pos[to] = pos[from];
        memcpy(son[to], son[from], sizeof(son[to]));
    }

    void insert(int c, int lstp)
    {
        int p = lst, np = lst = ++cur;
        len[np] = len[p] + 1, pos[np] = lstp, cnt[np] = 1, nd[lstp] = cur;
        for ( ; p && (!son[p][c]); p = fa[p]) son[p][c] = np;
        if (!p) fa[np] = 1;
        else
        {
            int q = son[p][c];
            if (len[q] == len[p] + 1) fa[np] = q;
            else
            {
                int nq = ++cur;
                copy_node(nq, q);
                len[nq] = len[p] + 1;
                fa[q] = fa[np] = nq;
                for ( ; p && (son[p][c] == q); p = fa[p]) son[p][c] = nq;
            }
        }
    }

    void sort()
    {
        for (int i = 1; i <= cur; i++) tot[len[i]]++;
        for (int i = 1; i <= m; i++) tot[i] += tot[i - 1];
        for (int i = 1; i <= cur; i++) seq[tot[len[i]]--] = i;
    }

    void build()
    {
        for (int i = 1; i <= m; i++) insert(str[i] - 'a', i);
        memset(tot, 0, (m + 1) * sizeof(int));
        sort();
        for (int i = cur; i >= 2; i--)
        {
            int u = seq[i];
            cnt[fa[u]] += cnt[u];
            if (fa[u] != 1) to[fa[u]][str[pos[u] - len[fa[u]]] - 'a'] = u;
        }
    }

    int nxt(int u, int l, char c)
    {
        if (len[u] >= l) return (str[pos[u] - l + 1] == c ? u : 0);
        return to[u][c - 'a'];
    }

    void dfs(int u, int p, int len, int cur)
    {
        if (!cur) return;
        f[cur]++;
        for (int v : g[u])
        {
            if ((v == p) || vis[v]) continue;
            dfs(v, u, len + 1, nxt(cur, len + 1, s[v]));
        }
    }

    void calc()
    {
        for (int i = 2; i <= cur; i++)
        {
            int u = seq[i];
            f[u] += f[fa[u]];
        }
    }
} s1, s2;

void get_sz(int u, int f, int sum)
{
    int mx = -1;
    sz[u] = 1;
    for (int v : g[u])
    {
        if ((v == f) || vis[v]) continue;
        get_sz(v, u, sum);
        sz[u] += sz[v];
        mx = max(mx, sz[v]);
    }
    mx = max(mx, sum - sz[u]);
    if (mx < sz_max) rt = u, sz_max = mx;
}

void get_rt(int u, int sum)
{
    sz_max = inf;
    get_sz(u, 0, sum);
}

void dfs1(int u, int fa)
{
    q[++qlen] = u;
    for (int v : g[u])
    {
        if ((v == fa) || vis[v]) continue;
        up[v] = u;
        dfs1(v, u);
    }
}

void dfs2(int u, int fa, int cur, int nega)
{
    if (!cur) return;
    // printf("%d %d\n", nega, s1.cnt[cur]);
    ans += nega * s1.cnt[cur];
    for (int v : g[u])
    {
        if ((v == fa) || vis[v]) continue;
        dfs2(v, u, s1.son[cur][rc[v]], nega);
    }
}

void calc1(int u, int c, int nega)
{
    s1.clear(), s2.clear();
    if (nega == 1)
    {
        s1.dfs(u, 0, 1, s1.son[1][rc[u]]);
        s2.dfs(u, 0, 1, s2.son[1][rc[u]]);
    }
    else
    {
        s1.dfs(u, 0, 2, s1.nxt(s1.son[1][c], 2, s[u]));
        s2.dfs(u, 0, 2, s2.nxt(s2.son[1][c], 2, s[u]));
    }
    s1.calc(), s2.calc();
    for (int i = 1; i <= m; i++) ans += 1ll * nega * s1.f[s1.nd[i]] * s2.f[s2.nd[m - i + 1]];
    // for (int i = 1; i <= m; i++)
    // {
        // printf("cur : %d %d %d %d %d\n", nega, s1.f[s1.nd[i]], s2.f[s2.nd[m - i + 1]], s1.nd[i], s2.nd[m - i + 1]);
        // ans += 1ll * nega * s1.f[s1.nd[i]] * s2.f[s2.nd[m - i + 1]];
    // }
}

void calc2(int u, int rt)
{
    qlen = 0, up[u] = rt;
    dfs1(u, 0);
    for (int i = 1; i <= qlen; i++)
    {
        int cur = q[i], nd = 1;
        while (cur != rt) nd = s1.son[nd][rc[cur]], cur = up[cur];
        nd = s1.son[nd][rc[cur]], nd = s1.son[nd][rc[u]];
        dfs2(u, 0, nd, -1);
    }
}

void solve(int u)
{
    if (sz_sum <= block)
    {
        qlen = 0, dfs1(u, 0);
        for (int i = 1; i <= qlen; i++) dfs2(q[i], 0, s1.son[1][rc[q[i]]], 1);
        return;
    }
    int cur_sz = sz_sum;
    vis[u] = true;
    calc1(u, 1, 1);
    for (int v : g[u])
    {
        if (vis[v]) continue;
        int to_sz = (sz[u] < sz[v] ? cur_sz - sz[u] : sz[v]);
        if (to_sz > block) calc1(v, rc[u], -1);
        else calc2(v, u);
    }
    for (int v : g[u])
    {
        if (vis[v]) continue;
        sz_sum = (sz[u] < sz[v] ? cur_sz - sz[u] : sz[v]);
        get_rt(v, sz_sum);
        solve(rt);
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    block = sqrt(n);
    for (int i = 1, u, v; i <= n - 1; i++)
    {
        scanf("%d%d", &u, &v);
        g[u].push_back(v);
        g[v].push_back(u);
    }
    scanf("%s%s", s + 1, t + 1);
    for (int i = 1; i <= n; i++) rc[i] = s[i] - 'a';
    for (int i = 1; i <= m; i++) s1.str[i] = t[i], s2.str[i] = t[m - i + 1];
    s1.build(), s2.build();
    sz_sum = n, get_rt(1, n);
    solve(rt);
    printf("%lld\n", ans);
    return 0;
}

/*
3 5
1 2
1 3
aab
abaab

15
*/

标签:sz,cur,sam,int,题解,len,fa,CTSC2010,P4218
From: https://www.cnblogs.com/lingspace/p/p4218.html

相关文章

  • SDK更新不了问题解决
    问题阐述相信大家在更新SDK的时候都会遇到更新不了的问题,而且打不开Google搜索,这是因为天朝墙了Google,所以要么只能通过科学上网或者改HOSTS才能访问,更新SDK!本节来介绍两种......
  • 牛客小白月赛65 D题 题解
    原题链接题意描述一共有两堆石子,第一堆有\(a\)个,第二堆有\(b\)个,牛牛和牛妹轮流取石子,牛牛先手,每次取石子的时候只能从以下\(2\)种方案种挑一种来取(对于选择的方案......
  • NOI2003 文本编辑器 题解
    \STL大法好/正规解法块状链表,这里采取的是黑科技解法。rope是扩展STL库中的一个数据结构——可持久化平衡树,相比较set,它更适合区间插入和删除。这里用来解此题,就显得十......
  • CF1779C Least Prefix Sum 题解
    可能更好的阅读体验题目传送门题目大意给定一个序列\(a\),长度\(n\)。每次操作可以把\(a_i\)变成\(-a_i\)。要求\(a\)做前缀和之后的序列\(s\)中最小值为\(s......
  • CF1779D Boris and His Amazing Haircut 题解
    可能更好的阅读体验题目传送门题目翻译题目解析如果有\(a_i<b_i\)直接输出NO。我们发现:如果\(b_l=b_r=x\)并且所有的\(l\lei\ler\)都有\(b_i\lex\)那么......
  • CF1779A Hall of Fame 题解
    可能更好的阅读体验题目传送门题目翻译有\(n\)个纪念碑以及对应的\(n\)个台灯。给定一个由L和R组成的序列\(a\),代表台灯的朝向。如果第\(i\)个台灯为L,代......
  • 【题解】P4027 [NOI2007] 货币兑换
    题意好长,但是不想概括了。\cdq/!思路cdq分治+凸包。首先考虑令\(f_i\)表示第\(i\)天可以得到的最大金额,\(f_1=s\)因为\(rate_i\)是常数,并且保证存在最优方......
  • 【题解】CF1178G The Awesomest Vertex
    题意给定一棵大小为\(n\)的树以及\(m\)个操作,定义一个结点\(u\)的权值为:\(|\sum\limits_{w\inR(v)}a_w|\cdot|\sum\limits_{w\inR(v)}b_w|\)其中\(R(v)......
  • 230102模拟题解
    t1容易发现对于奇数和偶数,能满足条件的长度分别是单调的,所以可以分别二分答案检查。检查的时候对于差分序列做哈希即可,然后用set/map/哈希表判\(B\)的子段是否有\(A......
  • CF865B Ordering Pizza 题解
    简要题意:有\(n\)个人去披萨店吃披萨,有两种披萨,每个披萨有\(m\)片。现在第\(i\)个人要吃\(c_i\)片披萨,如果吃一片第一种披萨会获得\(a_i\)的幸运值,如果吃一片第二......