BZOJ 4556 [Tjoi2016&Heoi2016]字符串

后缀数组+可持久化线段树+二分

啊啊啊智商好低,想了好久。

一个直观的想法是在s[a…b]中找到和s[c…d]最接近的串,使得height最大。然而一个很烦的事情是s[a…b]存在一个右边界b,意味着我们需要min一个边界值。遇到这种min啊max啊的东西一般考虑强行分类讨论。

于是二分一个答案mid,那能贡献mid答案的串开头一定位于s[a…b-mid+1]之中,那边界的条件就没了,这样就可以直接用height了。这东西可持久化线段树套一下即可。

#include
#include
#include
#define N 200005
#define cmin(u,v) (u)>(v)?(u)=(v):0
#define cmax(u,v) (u)<(v)?(u)=(v):0
using namespace std;
namespace runzhe2000
{
    const int INF = 1<<30;
    char s[N];
    int n, m, t1[N], t2[N], sa[N], rank[N], sum[N], height[N], lef, rig;
    void SA_build()
    {
        int *x = t1, *y = t2, m = 30;
        for(int i = 1; i <= n; i++) sum[x[i] = s[i] - 'a' + 1]++;
        for(int i = 1; i <= m; i++) sum[i] += sum[i-1];
        for(int i = n; i >= 1; i--) sa[sum[x[i]]--] = i;
        for(int k = 1; k <= n; k <<= 1)
        {
            int p = 0;
            for(int i = n-k+1; i <= n; i++) y[++p] = i;
            for(int i = 1; i <= n; i++) if(sa[i] - k > 0) y[++p] = sa[i] - k;

            for(int i = 1; i <= m; i++) sum[i] = 0;
            for(int i = 1; i <= n; i++) sum[x[i]]++;
            for(int i = 1; i <= m; i++) sum[i] += sum[i-1];
            for(int i = n; i >= 1; i--) sa[sum[x[y[i]]]--] = y[i];

            swap(x, y);
            for(int i = 1; i <= n; i++)
                x[sa[i]] = x[sa[i-1]] + (y[sa[i]] == y[sa[i-1]] && y[sa[i]+k] == y[sa[i-1]+k] ? 0 : 1);
            m = x[sa[n]];
            if(m == n) break;
        }
        for(int i = 1; i <= n; i++) rank[sa[i]] = i;
        for(int i = 1, k = 0; i <= n; height[rank[i++]] = k?k--:k)
            for(; s[i+k] == s[sa[rank[i]-1]+k]; k++);
    }
    struct seg
    {
        int mi, cnt;
        seg *ch[2];
        void pushup()
        {
            mi = min(ch[0]->mi, ch[1]->mi);
            cnt = ch[0]->cnt + ch[1]->cnt;
        }
    }mem[N*15], *tot, *null, *root[N];
    seg* seg_new()
    {
        seg *p = ++tot;
        *p = *null;
        return p;
    }
    void seg_init()
    {
        null = tot = mem;
        null->mi = INF, null->cnt = 0;
        null->ch[0] = null->ch[1] = null;
        for(int i = 0; i <= n; i++) root[i] = seg_new();
    }
    void seg_make(seg *x, int l, int r)
    {
        if(l == r){x -> mi = height[l]; return;}
        int mid = (l+r)>>1;
        x->ch[0] = seg_new();
        x->ch[1] = seg_new();
        seg_make(x->ch[0], l, mid);
        seg_make(x->ch[1], mid+1, r);
        x->pushup();
    }
    void seg_ins(seg *x, seg *y, int l, int r, int pos)
    {
        *y = *x;
        if(l == r){y->cnt++; return;};
        int mid = (l+r)>>1;
        if(pos <= mid)
        {
            y->ch[0] = seg_new(); y->ch[1] = x->ch[1];
            seg_ins(x->ch[0], y->ch[0], l, mid, pos);
        }
        else
        {
            y->ch[0] = x->ch[0]; y->ch[1] = seg_new();
            seg_ins(x->ch[1], y->ch[1], mid+1, r, pos);
        }
        y->pushup();
    }
    bool find(seg *x, seg *y, int l, int r, int ql, int qr, int isright)
    {
        if(ql > qr) return 0;
        if(ql <= l && r <= qr)
        {
            if(y->cnt - x->cnt == 0) return 0;
            else if(l == r) 
            {
                if(!isright) cmax(lef, l);
                else cmin(rig, l);
                return 1;
            }
        }
        int mid = (l+r)>>1;
        if(!isright) // left max
        {
            if(mid < qr) {if(find(x->ch[1], y->ch[1], mid+1, r, ql, qr, isright)) return 1;}
            if(ql <= mid) return find(x->ch[0], y->ch[0], l, mid, ql, qr, isright);
        }
        else // right min
        {
            if(ql <= mid) {if(find(x->ch[0], y->ch[0], l, mid, ql, qr, isright)) return 1;}
            if(mid < qr) return find(x->ch[1], y->ch[1], mid+1, r, ql, qr, isright);
        }
    }
    int query(seg *x, int l, int r, int ql, int qr)
    {
        if(ql > qr) return INF;
        if(ql <= l && r <= qr)return x->mi;
        int mid = (l+r)>>1, p1 = INF, p2 = INF;
        if(ql <= mid) p1 = query(x->ch[0], l, mid, ql, qr);
        if(mid < qr) p2 = query(x->ch[1], mid+1, r, ql, qr);
        return min(p1, p2);
    }
    void main()
    {
        int happy = scanf("%d%d%s",&n,&m,s+1);
        s[++n] = 'z' + 1;
        SA_build();
        seg_init();
        seg_make(root[0],1,n);
        for(int i = 1; i <= n; i++)
            seg_ins(root[i-1], root[i], 1, n, rank[i]);         
        for(int i = 1; i <= m; i++)
        {
            int a, b, c, d, p, l, r;
            happy = scanf("%d%d%d%d",&a,&b,&c,&d);
            l = 0, r = min(d-c+1, b-a+1);
            for(; l < r; )
            {
                int mid = (l+r+1)>>1, ans; lef = 0; rig = n;
                find(root[a-1], root[b-mid+1], 1, n, 1, rank[c], 0);
                find(root[a-1], root[b-mid+1], 1, n, rank[c], n, 1);
                ans = max(query(root[0], 1, n, lef+1, rank[c]), query(root[0], 1, n, rank[c]+1, rig));
                cmin(ans, mid);
                if(ans < mid) r = mid - 1;
                else l = mid;
            }
            printf("%d\n",l);
        }
    }
}
int main()
{
    runzhe2000::main();
}

你可能感兴趣的:(字符串-后缀,数据结构-可持久化线段树,其它-二分/三分)