LOJ2479 「九省联考 2018」制胡窜

题意

题目描述

对于一个字符串S ,我们定义$|S|$ 表示S 的长度。

接着,我们定义$S_i$ 表示S 中第i 个字符,$S_{L,R}$ 表示由S 中从左往右数,第L 个字符到第R 个字符依次连接形成的字符串。特别的,如果L > R ,或者$L \notin [1, |S|]$, 或者$R \notin [1, |S|]$ 我们可以认为$S_{L,R} $为空串。

给定一个长度为n 的仅由数字构成的字符串S ,现在有q 次询问,第k 次询问会 给出S 的一个字符串$S_{l,r}$ ,请你求出有多少对(i, j),满足1 <= i < j <= n,i + 1 < j,且$S_{l,r}$ 出现在$S_{1,i}$ 中或$S_{i+1,j-1}$中或$S_{j,n}$ 中。

输入输出格式

输入格式:

从文件cutting.in 中读入数据。

输入的第一行包含两个整数n, q。

第二行包含一个长度为n 的仅由数字构成的字符串S 。

接下来q 行,每行两个正整数l 和r,表示此次询问的子串是$S_{l,r}$。

输出格式:

输出到文件cutting.out 中。

对于每个询问,输出一个整数表示合法的数对个数。

输入输出样例

输入样例#1: 复制
5 2
00100
1 2
1 3
输出样例#1: 复制
5
1

说明

分析

参照cz_xuyixuanTS_Hugh的题解。

问题跟所有子串有关,考虑后缀自动机。

问题转化

题目中两个“或”已经说明了正面求很难做。正难则反,计算\(i,j\)分布使得三段都不包含所有出现的串的方案数。显然\((i,i+1),(j-1,j)\)这两个空隙要切断所有的串。所以问题转化到了空隙上面,把方案数求出来用\(\binom{n-1}2\)减去它就是答案了。

下面的论述为了方便,以右端点代替空隙,即用\(i\)来代替\((i-1,i)\)这个空隙,显然\(i,j\in[2,n],i<j\)

只询问一个串

首先,一个询问的答案只和询问串的在主串中所有出现的位置有关。因此要找出询问串在后缀自动机上的位置。

定位一个询问的串可以在后缀自动机parent树上倍增在\(O(\log n)\)的时间内完成。如果能预处理出right集合那么所有出现的位置就找到了。

考虑用线段树合并来做这件事。现在我们有了一棵维护着所有询问串出现位置的右端点的线段树,考虑如何得到答案。

考虑较靠前的断点切断了哪些字符串。我们需要求出的即是:
\[ \sum_{k=ql}^qr询问串第一次出现和第k次出现的交∗询问串最后一次出现和第(k+1)次出现的交 \]
其中\([ql,qr]\)\(k\)可能的取值范围。\(ql\)即第\(k+1\)个串与后面的串有交集时,\(k\)的可取最小值;类似的,\(qr\)即第\(k\)和前面的串有交集时,\(k\)的可取最大值。它们可以在线段树上二分得到。

记询问串第\(k\)次出现的左端点和右端点分别为\(L_k\)\(R_k\)
对于询问串第一次出现和第\(k\)次出现的交,它在大部分的情况下为\(L_{k+1}-L_k\),也即\(R_{k+1}-R_k\),只有在\(k=qr\)时,它有可能为\(R_1−L_i+1\)
对于询问串最后一次出现和第\(k+1\)次出现的交,它在大部分情况下为\(R_{k+1}−L_{last}+1\)

还需要考虑一些特殊情况,可能可以用\(i,j\)中的一个切断所有串。为了不重不漏,对\(i\)来讨论,分为两种情况。

  1. \(i\)不切断任意的字符串,那么它一定在询问串第一次出现之前,并且\(j\)要恰好切断所有字符串,也即\(j\)的取值范围是询问串所有出现位置的并。
    只有在\(k=ql\)时,有可能对应这种情况,此时\(ql=0\)
  2. \(i\)切断了所有的字符串,那么\(j\)只需要满足在较靠前的断点之后即可,因此在这种情况下可能的方案数是一个等差数列的各项之和。
    只有在\(k=qr\)时有可能出现这种情况,此时\(qr=last\)

那么,我们可以对\(k=ql\),和\(k=qr\)时特殊处理。对于剩下的情况,也即\(k\in(ql,qr)\)时,我们需要求出
\[ \sum^{qr−1}_{k=ql+1}(R_{k+1}−R_k)∗(R_{k+1}−L_{last}+1) \\ =\sum^{qr−1}_{k=ql+1}(R_{k+1}−R_k)∗R_{k+1}−(L_{last}−1)(R_{qr}−R_{ql+1}) \]
对于求和部分,是关于两个相邻位置的信息,我们可以在线段树上额外维护这个信息。那么一个串就做完了。

询问多个串

由于使用线段树合并要求在线的话,合并的时候必须新建节点,所以空间复杂度就错了。所以把询问离线挂在parent树上,询问的时候在parent按从叶子到根的拓扑序来做。这样线段树合并的时候直接用原来的节点就行了。合并时均摊分析,最坏复杂度\(O(n\log^2 n)\)

然后这道题就做完了。时间复杂度\(O(n\log^2+q\log n)\)

代码

这题给的数字串……写的字母串调了好久。

#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
    rg T data=0;rg char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-') data=-data;
    for(;isdigit(ch);ch=getchar()) data=data*10+ch-'0';
    return data;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll;
using namespace std;

co int N=2e5;
int n,m;
char s[N];
ll ans[300001];
// Interval Tree
struct node{int min,max;ll sum;};
node operator+(co node&a,co node&b){
    node c=(node){a.min,b.max,a.sum+b.sum};
    if(!c.min) c.min=b.min;
    if(!c.max) c.max=a.max;
    if(a.max&&b.min) c.sum+=(ll)b.min*(b.min-a.max);
    return c;
}
namespace T{
    node t[N*17];
    int tot,lc[N*17],rc[N*17];
    void insert(int&x,int l,int r,int p){
        if(!x) x=++tot;
        if(l==r) return t[x]=(node){l,l,0},void();
        int mid=l+r>>1;
        if(p<=mid) insert(lc[x],l,mid,p);
        else insert(rc[x],mid+1,r,p);
        t[x]=t[lc[x]]+t[rc[x]];
    }
    int merge(int x,int y){
        if(!x||!y) return x+y;
        lc[x]=merge(lc[x],lc[y]),rc[x]=merge(rc[x],rc[y]);
        t[x]=t[lc[x]]+t[rc[x]];
        return x;
    }
    int lower(int x,int l,int r,int p){ // first >=
        if(t[x].min>=p) return t[x].min;
        int mid=l+r>>1;
        if(t[lc[x]].max&&t[lc[x]].max>=p) return lower(lc[x],l,mid,p);
        else return lower(rc[x],mid+1,r,p);
    }
    int upper(int x,int l,int r,int p){ // last <=
        if(t[x].max<=p) return t[x].max;
        int mid=l+r>>1;
        if(t[rc[x]].min&&t[rc[x]].min<=p) return upper(rc[x],mid+1,r,p);
        else return upper(lc[x],l,mid,p);
    }
    node query(int x,int l,int r,int ql,int qr){
        if(ql>qr) return (node){0,0,0};
        if(ql<=l&&r<=qr) return t[x];
        int mid=l+r>>1;
        if(qr<=mid) return query(lc[x],l,mid,ql,qr);
        if(ql>mid) return query(rc[x],mid+1,r,ql,qr);
        return query(lc[x],l,mid,ql,qr)+query(rc[x],mid+1,r,ql,qr);
    }
    ll query(int x,int len){ // represent gap with right vertice
        if(len<=0) return 0;
        int r1=t[x].min,l1=r1-len+1;
        int rn=t[x].max,ln=rn-len+1;
        int ql=lower(x,1,n,ln);
        if(ql!=r1) ql=upper(x,1,n,ql-1);
        else ql=0;
        int qr=upper(x,1,n,r1+len-1);
        if(ql>qr) return 0;
        ll ans=0;
        if(ql==qr){
            assert(0<ql&&ql<rn);
            ans+=(ll) (min(r1,lower(x,1,n,ql+1)-len)-(ql-len+1)+1) * (lower(x,1,n,ql+1)-ln+1);
        }
        else{
            node tmp=query(x,1,n,ql+1,qr);
            ans+=tmp.sum-(ll) (ln-1) * (tmp.max-tmp.min);
            if(ql==0) ans+=(ll) (l1-2) * (r1-ln+1);
            else ans+=(ll) (min(r1,lower(x,1,n,ql+1)-len)-(ql-len+1)+1) * (lower(x,1,n,ql+1)-ln+1);
            if(qr==rn) ans+=(ll) (n-r1+n-ln) * (r1-ln+1) / 2;
            else ans+=(ll) (min(r1,lower(x,1,n,qr+1)-len)-(qr-len+1)+1) * (lower(x,1,n,qr+1)-ln+1);
        }
        return ans;
    }
}
// Suffix Automaton
namespace SAM{
    int last=1,tot=1;
    int ch[N][10],fa[N],len[N],pos[N]; // pos:out->in
    int root[N]; // for Interval Tree
    void extend(int c,int po){
        int p=last,cur=last=++tot;
        len[cur]=len[p]+1,pos[po]=cur;
        T::insert(root[cur],1,n,po);
        for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=cur;
        if(!p) fa[cur]=1;
        else{
            int q=ch[p][c];
            if(len[q]==len[p]+1) fa[cur]=q;
            else{
                int clone=++tot;
                memcpy(ch[clone],ch[q],sizeof ch[q]);
                fa[clone]=fa[q],len[clone]=len[p]+1;
                fa[cur]=fa[q]=clone;
                for(;ch[p][c]==q;p=fa[p]) ch[p][c]=clone;
            }
        }
    }
    int anc[N][19];
    vector<int> e[N];
    vector<pair<int,int> > q[N];
    void init(){
        for(int i=1;i<=n;++i) extend(s[i]-'0',i);
        for(int i=1;i<=tot;++i) anc[i][0]=fa[i],e[fa[i]].push_back(i);
        for(int k=1;k<=18;++k)
            for(int i=1;i<=tot;++i) anc[i][k]=anc[anc[i][k-1]][k-1];
    }
    void storequery(int l,int r,int id){
        int len=r-l+1,p=pos[r];
        for(int i=18;i>=0;--i)
            if(SAM::len[anc[p][i]]>=len) p=anc[p][i];
        q[p].push_back(make_pair(len,id));
    }
    void work(int p){
        for(int i=0;i<e[p].size();++i)
            work(e[p][i]),root[p]=T::merge(root[p],root[e[p][i]]);
        for(int i=0;i<q[p].size();++i)
            ans[q[p][i].second]=(ll)(n-1)*(n-2)/2-T::query(root[p],q[p][i].first-1); // available len for cutting
    }
}

int main(){
    read(n),read(m),scanf("%s",s+1);
    SAM::init();
    for(int l,r,i=1;i<=m;++i){
        read(l),read(r);
        SAM::storequery(l,r,i);
    }
    SAM::work(1);
    for(int i=1;i<=m;++i) printf("%lld\n",ans[i]);
    return 0;
}

强烈推荐cz_xuyixuan的代码,他竟然把码风维护到这种题上。当初我早就放弃封装了。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
const int MAXP = 200005;
const int MAXQ = 300005;
const int MAXS = 3e6 + 5;
const int MAXLOG = 20;
const int MAXC = 10;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
    x = 0; int f = 1;
    char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
    for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
    x *= f;
}
template <typename T> void write(T x) {
    if (x < 0) x = -x, putchar('-');
    if (x > 9) write(x / 10);
    putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
    write(x);
    puts("");
}
struct query {int l, r, home; };
struct info {int Min, Max; long long sum; };
info operator + (info a, info b) {
    info ans = (info) {a.Min, b.Max, a.sum + b.sum};
    if (ans.Min == 0) ans.Min = b.Min;
    if (ans.Max == 0) ans.Max = a.Max;
    if (a.Max && b.Min) ans.sum += 1ll * b.Min * (b.Min - a.Max);
    return ans;
}
struct SegmentTree {
    struct Node {
        int lc, rc;
        info val;
    } a[MAXS];
    int size, n;
    void init(int x) {
        n = x;
        size = 0;
    }
    void update(int root) {
        a[root].val = a[a[root].lc].val + a[a[root].rc].val;
    }
    void insert(int &root, int l, int r, int pos) {
        if (root == 0) root = ++size;
        if (l == r) {
            a[root].val = (info) {l, l, 0};
            return;
        }
        int mid = (l + r) / 2;
        if (mid >= pos) insert(a[root].lc, l, mid, pos);
        else insert(a[root].rc, mid + 1, r, pos);
        update(root);
    }
    void insert(int &root, int val) {
        insert(root, 1, n, val);
    }
    int merge(int x, int y) {
        if (x == 0 || y == 0) return x + y;
        a[x].lc = merge(a[x].lc, a[y].lc);
        a[x].rc = merge(a[x].rc, a[y].rc);
        update(x);
        return x;
    }
    void join(int &x, int y) {
        x = merge(x, y);
    }
    int lower(int root, int l, int r, int val) {
        if (a[root].val.Min >= val) return a[root].val.Min;
        int mid = (l + r) / 2;
        if (a[a[root].lc].val.Max != 0 && a[a[root].lc].val.Max >= val) return lower(a[root].lc, l, mid, val);
        else return lower(a[root].rc, mid + 1, r, val);
    }
    int upper(int root, int l, int r, int val) {
        if (a[root].val.Max <= val) return a[root].val.Max;
        int mid = (l + r) / 2;
        if (a[a[root].rc].val.Min != 0 && a[a[root].rc].val.Min <= val) return upper(a[root].rc, mid + 1, r, val);
        else return upper(a[root].lc, l, mid, val);
    }
    info query(int root, int l, int r, int ql, int qr) {
        if (ql > qr) return (info) {0, 0, 0};
        if (l == ql && r == qr) return a[root].val;
        int mid = (l + r) / 2;
        if (mid >= qr) return query(a[root].lc, l, mid, ql, qr);
        else if (mid + 1 <= ql) return query(a[root].rc, mid + 1, r, ql, qr);
        else return query(a[root].lc, l, mid, ql, mid) + query(a[root].rc, mid + 1, r, mid + 1, qr);
    }
    long long sum(int l, int r) {
        if (l > r) swap(l, r);
        return (l + r) * (r - l + 1ll) / 2;
    }
    long long query(int root, int l, int r) {
        if (l == r) return 0;
        int len = r - l;
        int r1 = a[root].val.Min, l1 = r1 - len + 1;
        int rn = a[root].val.Max, ln = rn - len + 1;
        int ql = lower(root, 1, n, ln);
        if (ql != r1) ql = upper(root, 1, n, ql - 1);
        else ql = 0;
        int qr = upper(root, 1, n, r1 + len - 1);
        long long ans = 0;
        if (ql > qr) return 0;
        if (ql == qr) {
            if (ql == 0) ans += (l1 - 2ll) * (r1 - ln + 1ll);
            else if (qr == rn) ans += sum(n - r1, n - ln);
            else ans += (min(r1, lower(root, 1, n, ql + 1) - len) - (ql - len + 1) + 1ll) * (lower(root, 1, n, ql + 1) - ln + 1ll);
        } else {
            info tmp = query(root, 1, n, ql + 1, qr);
            ans += tmp.sum - (ln - 1ll) * (tmp.Max - tmp.Min);
            if (ql == 0) ans += (l1 - 2ll) * (r1 - ln + 1ll);
            else ans += (min(r1, lower(root, 1, n, ql + 1) - len) - (ql - len + 1) + 1ll) * (lower(root, 1, n, ql + 1) - ln + 1ll);
            if (qr == rn) ans += sum(n - r1, n - ln);
            else ans += (min(r1, lower(root, 1, n, qr + 1) - len) - (qr - len + 1) + 1ll) * (lower(root, 1, n, qr + 1) - ln + 1ll);
        }
        return ans;
    }
} ST;
struct SuffixAutomaton {
    int root, size, last, len;
    int child[MAXP][MAXC], home[MAXN];
    int father[MAXP][MAXLOG], sroot[MAXP];
    int fail[MAXP], depth[MAXP], cnt[MAXP];
    vector <int> a[MAXP];
    vector <query> q[MAXP];
    long long ans[MAXQ];
    int newnode(int dep) {
        fail[size] = 0;
        depth[size] = dep;
        memset(child[size], 0, sizeof(child[size]));
        return size++;
    }
    void extend(int ch, int from) {
        int p = last, np = newnode(depth[last] + 1);
        while (child[p][ch] == 0) {
            child[p][ch] = np;
            p = fail[p];
        }
        if (child[p][ch] == np) fail[np] = root;
        else {
            int q = child[p][ch];
            if (depth[q] == depth[p] + 1) fail[np] = q;
            else {
                int nq = newnode(depth[p] + 1);
                fail[nq] = fail[q];
                fail[q] = fail[np] = nq;
                memcpy(child[nq], child[q], sizeof(child[q]));
                while (child[p][ch] == q) {
                    child[p][ch] = nq;
                    p = fail[p];
                }
            }
        }
        cnt[last = np]++;
        ST.insert(sroot[np], from);
        home[from] = np;
    }
    void init(char *s) {
        size = 0;
        root = last = newnode(0);
        len = strlen(s + 1);
        for (int i = 1; i <= len; i++)
            extend(s[i] - '0', i);
        for (int i = 1; i < size; i++) {
            father[i][0] = fail[i];
            a[fail[i]].push_back(i);
        }
        for (int p = 1; p < MAXLOG; p++)
        for (int i = 0; i < size; i++)
            father[i][p] = father[father[i][p - 1]][p - 1];
    }
    void storequery(int l, int r, int from) {
        int len = r - l + 1, pos = home[r];
        for (int i = MAXLOG - 1; i >= 0; i--)
            if (depth[father[pos][i]] >= len) pos = father[pos][i];
        q[pos].push_back((query) {l, r, from});
    }
    void work(int pos) {
        for (unsigned i = 0; i < a[pos].size(); i++) {
            work(a[pos][i]);
            ST.join(sroot[pos], sroot[a[pos][i]]);
        }
        for (unsigned i = 0; i < q[pos].size(); i++)
            ans[q[pos][i].home] = (len - 1ll) * (len - 2ll) / 2 - ST.query(sroot[pos], q[pos][i].l, q[pos][i].r);
    }
    void solve(int m) {
        work(0);
        for (int i = 1; i <= m; i++)
            printf("%lld\n", ans[i]);
    }
} SAM;
int n, m;
char s[MAXN];
int main() {
    read(n), read(m);
    ST.init(n);
    scanf("%s", s + 1);
    SAM.init(s);
    for (int i = 1; i <= m; i++) {
        int l, r; read(l), read(r);
        SAM.storequery(l, r, i);
    }
    SAM.solve(m);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/autoint/p/10809248.html