[NOI2016]优秀的拆分

题目实际上要求我们求从每个点出发的AA串的数量

考虑点i的答案,发现如果前缀i与前缀j(j<=i)的最长公共后缀>=i-j,那么i点出发向前就存在一个长度为i-j的AA串,题目即求对于每个前缀,有多少个在他之前的前缀满足条件

考虑后缀自动机,由于每个前缀都是后缀自动机parent树上的一点,即两个前缀的最长公共后缀为两个前缀点在parent树上的lca的len长度。考虑在lca处统计贡献。

于是每个点都建立一颗动态线段树,要统计子树之间相互的贡献,按照启发式合并合并线段树,即继承重儿子的动态线段树,暴力将轻儿子的线段树上的节点合并到重儿子,统计贡献,具体会用到区间加(lazy_tag)和区间查询,时间复杂度O(Tnlog^2)

 

#include <cmath>
#include <cstdio>
#include <vector>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mm(a, b) memset(a, b, sizeof(a))
#define LL long long

using namespace std;

const int Maxn = 100010;

int Len_s = 0;

char s[Maxn], Rev[Maxn];
int Ans[Maxn], id[Maxn * 4], Root[Maxn * 4], Ansl[Maxn], Ansr[Maxn];
int Size[Maxn * 26], lc[Maxn * 26], rc[Maxn * 26], tag[Maxn * 26];
int Vec_tot, Sam_tot, Seg_tot, Head, Last_prefix;
vector<int> Tree[Maxn * 4], Tree_suf[Maxn];

struct node {
    int ch[26];
    int fa, Len;
} dot[Maxn * 2];

void Init() { Sam_tot = Head = Last_prefix = 1; }

void pushdown(int h, int l, int r) {
    if (!h)
        return;
    if (!tag[h])
        return;
    if (l == r) {
        Ans[l] += tag[h];
        tag[h] = 0;
        return;
    }

    if (lc[h])
        tag[lc[h]] += tag[h];
    if (rc[h])
        tag[rc[h]] += tag[h];
    tag[h] = 0;
}

void Insert(int &h, int l, int r, int loc) {
    pushdown(h, l, r);
    if (!h)
        h = ++Seg_tot;
    if (l == r) {
        Size[h] = 1;
        return;
    }

    int mid = l + r >> 1;
    if (loc <= mid)
        Insert(lc[h], l, mid, loc);
    else
        Insert(rc[h], mid + 1, r, loc);

    Size[h] = Size[lc[h]] + Size[rc[h]];
}

int Query_size(int h, int l, int r, int s, int e) {
    pushdown(h, l, r);
    if (!h)
        return 0;
    if (l == s && r == e)
        return Size[h];

    int mid = l + r >> 1;
    if (e <= mid)
        return Query_size(lc[h], l, mid, s, e);
    else if (s > mid)
        return Query_size(rc[h], mid + 1, r, s, e);
    else
        return Query_size(lc[h], l, mid, s, mid) + Query_size(rc[h], mid + 1, r, mid + 1, e);
}

void Query_sum(int h, int l, int r, int loc) {
    pushdown(h, l, r);
    if (l == r)
        return;

    int mid = l + r >> 1;
    if (loc <= mid)
        Query_sum(lc[h], l, mid, loc);
    else
        Query_sum(rc[h], mid + 1, r, loc);
}

void Add(int h, int l, int r, int s, int e) {
    if (!h)
        return;
    pushdown(h, l, r);
    if (l == s && r == e) {
        tag[h] = 1;
        pushdown(h, l, r);
        return;
    }

    int mid = l + r >> 1;
    if (e <= mid)
        Add(lc[h], l, mid, s, e);
    else if (s > mid)
        Add(rc[h], mid + 1, r, s, e);
    else {
        Add(lc[h], l, mid, s, mid);
        Add(rc[h], mid + 1, r, mid + 1, e);
    }
}

void Create_sam(int c) {
    int Cur = ++Sam_tot;
    dot[Cur].Len = dot[Last_prefix].Len + 1;

    id[Cur] = ++Vec_tot, Tree_suf[Vec_tot].push_back(dot[Cur].Len);
    Insert(Root[Cur], 1, Len_s, dot[Cur].Len);

    int Tmp, To;
    for (Tmp = Last_prefix; Tmp && !(To = dot[Tmp].ch[c]); Tmp = dot[Tmp].fa) {
        dot[Tmp].ch[c] = Cur;
    }

    Last_prefix = Cur;
    if (!Tmp)
        dot[Cur].fa = Head;
    else if (dot[Tmp].Len + 1 == dot[To].Len)
        dot[Cur].fa = To;
    else {
        ++Sam_tot;
        dot[Sam_tot] = dot[To];
        dot[Sam_tot].Len = dot[Tmp].Len + 1;

        dot[To].fa = dot[Cur].fa = Sam_tot;
        while (Tmp && dot[Tmp].ch[c] == To) {
            dot[Tmp].ch[c] = Sam_tot;
            Tmp = dot[Tmp].fa;
        }
    }
}

void Add_edge() {
    for (int i = 1; i <= Sam_tot; ++i) {
        if (dot[i].fa == 0)
            continue;
        Tree[dot[i].fa].push_back(i);
    }
}

void Calc_tree(int h) {
    if ((int)Tree[h].size() == 0)
        return;
    int Maxval = 0, Maxid = 0;

    for (int i = 0; i < (int)Tree[h].size(); ++i) {
        int To = Tree[h][i];
        Calc_tree(To);

        if ((int)Tree_suf[id[To]].size() > Maxval) {
            Maxval = (int)Tree_suf[id[To]].size();
            Maxid = To;
        }
    }

    for (int i = 0; i < (int)Tree[h].size(); ++i) {
        int To = Tree[h][i];
        if (To == Maxid)
            continue;

        for (int j = 0; j < (int)Tree_suf[id[To]].size(); ++j) {
            int Node = Tree_suf[id[To]][j], Tmp;
            Query_sum(Root[To], 1, Len_s, Node);
            if ((Tmp = max(Node - dot[h].Len, 1)) <= Node - 1) {
                Ans[Node] += Query_size(Root[Maxid], 1, Len_s, Tmp, Node - 1);
            }

            if ((Tmp = min(Node + dot[h].Len, Len_s)) >= Node + 1) {
                Add(Root[Maxid], 1, Len_s, Node + 1, Tmp);
            }
        }

        for (int j = 0; j < (int)Tree_suf[id[To]].size(); ++j) {
            int Node = Tree_suf[id[To]][j];

            Insert(Root[Maxid], 1, Len_s, Node);
            Tree_suf[id[Maxid]].push_back(Node);
        }
    }
    //¿¼ÂDz»ÖÜËùÖÂ, »¹Óнڵ㱾ÉíΪǰ׺µÄÇé¿ö
    for (int i = 0; i < (int)Tree_suf[id[h]].size(); ++i) {
        int Node = Tree_suf[id[h]][i], Tmp;

        if ((Tmp = max(Node - dot[h].Len, 1)) <= Node - 1) {
            Ans[Node] += Query_size(Root[Maxid], 1, Len_s, Tmp, Node - 1);
        }

        if ((Tmp = min(Node + dot[h].Len, Len_s)) >= Node + 1) {
            Add(Root[Maxid], 1, Len_s, Node + 1, Tmp);
        }

        Insert(Root[Maxid], 1, Len_s, Node);
        Tree_suf[id[Maxid]].push_back(Node);
    }

    Root[h] = Root[Maxid];
    id[h] = id[Maxid];
}

void Arrange() {
    Init();
    for (int i = 1; i <= Len_s; ++i) {
        Create_sam(s[i] - 'a');
    }

    Add_edge();
    Calc_tree(Head);

    for (int i = 0; i < (int)Tree_suf[id[Head]].size(); ++i) {
        int Node = Tree_suf[id[Head]][i];
        Query_sum(Root[Head], 1, Len_s, Node);
    }
}

void Clear() {
    for (int i = 1; i <= Seg_tot; ++i) lc[i] = rc[i] = tag[i] = Size[i] = 0;
    for (int i = 1; i <= Len_s; ++i) Ans[i] = 0;
    for (int i = 1; i <= Vec_tot; ++i) Tree_suf[i].clear();
    for (int i = 1; i <= Sam_tot; ++i) {
        Tree[i].clear();
        dot[i].fa = dot[i].Len = Root[i] = id[i] = 0;
        for (int j = 0; j < 26; ++j) {
            dot[i].ch[j] = 0;
        }
    }

    Vec_tot = Sam_tot = Seg_tot = 0;
}

void file() {
    freopen("bona.in", "r", stdin);
    freopen("bona.out", "w", stdout);
}

int main() {

    file();

    int T;
    scanf("%d", &T);

    while (T--) {
        scanf("%s", s + 1);
        Len_s = strlen(s + 1);
        Arrange();
        for (int i = 1; i <= Len_s; ++i) Ansl[i] = Ans[i];
        Clear();

        for (int i = 1; i <= Len_s; ++i) Rev[i] = s[Len_s - i + 1];
        for (int i = 1; i <= Len_s; ++i) s[i] = Rev[i];

        Arrange();
        for (int i = 1; i <= Len_s; ++i) Ansr[i] = Ans[i];
        Clear();

        LL Sum = 0;
        for (int i = 1; i < Len_s; ++i) {
            Sum += 1ll * Ansl[i] * Ansr[Len_s - i];
        }
        printf("%lld\n", Sum);
    }

    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值