【GDOI2017 day1】微信

题目大意:

给出20棵trie。

每次询问问某几棵trie的子串的lcs。

总长小于10^6。

题解:

已经退役的wyx当时说:“又是裸题,真没意思。”

呵呵。可惜初二的我连后缀自动机是什么都不知道,只会那个弱弱的AC自动机。

如果没有每次的询问,那么这个跟SPOJ的lcs2没有什么区别,这个只是广义后缀自动机。

前一篇博客已经讲过,广义后缀自动机和正常的真的没有毛线区别。

现在重点在于每次询问某几棵怎么办?

只有20个trie启发我们暴力一次算出所有的答案,存下来,O(1)回答。

做法是合并二十个trie。

每个点存一个二进制状态表示从根到它的路径是哪些原来的trie的公共部分。

那么我们把这个点丢到SAM里去,它对应状态所代表的子串显然可以在这个二进制状态的基础上,所以or一下。

由于一个状态的right一定是虚边父亲的right的子集,所以Turpo一下,给它的父亲也or一下。

每个状态x所代表的最大长度是step[x],所以去个max。

最后注意如果有二进制状态x,y,x是y的子集,既然y所对应的lcs有这么长,x更少,那么一定也可以。所以-每个二进制状态也要给子状态传一下答案。

时间复杂度是 O(2nn+106)

Code:

#include<cstdio> 
#include<cstring>
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define max(a, b) ((a) > (b) ? (a) : (b))
using namespace std;

const int N = 1e6 + 5;

int a2[21];

int n, Q, x; char s[N];

int next[N][26], tot, last[N], fa[N], z[N];

int ans[1048577];

struct suffix_automation {
    int tot, fa[N * 2], son[N * 2][26], dep[N * 2], z[N * 2];
    #define push(v) dep[++ tot] = v;
    int Extend(int last, int c, int zz) {
        push(dep[last] + 1);
        int p = last, np = tot; z[np] |= zz;
        for(; p && !son[p][c]; p = fa[p]) son[p][c] = np;
        if(!p) fa[np] = 1; else {
            int q = son[p][c];
            if(dep[p] + 1 < dep[q]) {
                push(dep[p] + 1);
                int nq = tot; z[nq] |= zz;
                memcpy(son[nq], son[q], sizeof son[q]);
                fa[nq] = fa[q]; fa[q] = fa[np] = nq;
                for(; son[p][c] == q; p = fa[p]) son[p][c] = nq;
            } else fa[np] = q;
        }
        last = np;
        return last;
    }
    int d[N * 2], r[N * 2];
    void Turpo() {
        fo(i, 1, tot) r[fa[i]] ++;
        int st = 1, en = 0;
        fo(i, 1, tot) if(!r[i]) d[++ en] = i;
        for(; st <= en; st ++) {
            int x = d[st]; z[fa[x]] |= z[x];
            if(!(-- r[fa[x]])) d[++ en] = fa[x];
        }
    }
    void Gao() {
        fo(i, 1, tot) ans[z[i]] = max(ans[z[i]], dep[i]);
        for(int i = a2[n] - 1; i >= 1; i --) {
            fo(j, 0, n - 1) if(i & a2[j])
                ans[i - a2[j]] = max(ans[i - a2[j]], ans[i]);
        }
    }
} suf;

void dg(int x, int F, int c) {
    if(x == 0) {
        last[x] = suf.tot = 1;
    } else {
        last[x] = suf.Extend(last[F], c, z[x]);
    }
    fo(j, 0, 25) if(next[x][j]) {
        int y = next[x][j];
        dg(y, x, j);
    }
}


int main() {
    freopen("wechat.in", "r", stdin);
    freopen("wechat.out", "w", stdout);
    a2[0] = 1; fo(i, 1, 20) a2[i] = a2[i - 1] * 2;
    scanf("%d", &n);
    fo(i, 1, n) {
        scanf("%s", s);
        int m = strlen(s);
        x = 0; z[x] |= a2[i - 1];
        fo(j, 0, m - 1) {
            if(s[j] == '<') {
                x = fa[x];
            } else {
                int c = s[j] - 'a';
                if(next[x][c] == 0) next[x][c] = ++ tot, fa[tot] = x;
                x = next[x][c];
                z[x] |= a2[i - 1];
            }
        }
    }
    dg(0, 0, 0);
    suf.Turpo();
    suf.Gao();
    for(scanf("%d", &Q); Q; Q --) {
        scanf("%s", s);
        x = 0;
        fo(i, 0, n - 1) x += a2[i] * (s[i] == '1');
        printf("%d\n", ans[x]);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值