Luogu4384 [八省联考2018]制胡窜

Luogu4384 [八省联考2018]制胡窜

参考blog

这篇博客的分类讨论情况、式子和参考blog中是基本一致,因为蒟蒻切不了,只能够学习\(shadowice1984\)大佬,解析都是自己写的。

\(SAM+\)线段树合并+分类讨论

字符串太可怕了!!!

原问题即给定一个字符串\(S\),每次询问的字符串为原串的一个区间\(S(l,r)\),求有多少种方案把原字符串切成三段,使得\(S(l,r)\)是其中一段的子串。

转化一下,我们可以用总数减去不满足条件的切法,即三段中任何一段都不拥有子串\(S(l,r)\),总数很容易得出是\({n-1 \choose 2}\)

对于\(S\)中每一个\(T\)出现的区间\([l_i,r_i]\)(后面简称\(C_i\),总区间数为\(m\)),我们需要用两刀把所有区间都切断,这些区间的性质为长度相等。

设询问串为\(T\),在原字符串中第一次出现的位置和最后一次出现的区间为\(A,B\)\(A\)右端点\(first\)\(B\)左端点\(last\)

分类讨论(一定要画图):

\(1.\)\(S\)中含有三段互不相交的询问串,那么显然问题无解。

\[res_1=0 \]

\(2.\)\(A,B\)相交

\(2.1\)第一刀没有切完所有区间(保证第一刀在第二刀左侧,为了方便,会考虑部分切完所有区间的情况)

考虑在区间\([l_i,l_{i+1})\)切第一刀,它们必然是等价的,因为左端点必然小于等于每一个右端点。

为了满足切完所有区间,我们考虑下一刀的位置。首先,我们在区间\([l_i,l_{i+1})\)上切一刀,区间\(1 \sim i\)已经被切断了。那么我们需要切断后面的所有区间,显然我们至少要切在\(last\)位置才能切断\(B\),所以\(last\)为第二刀的左端点。那么右端点呢?我们需要避免两刀之间仍然含有区间的情况。我们暂时还没有切断区间\(C_{i+1}\),我们只需要使得第二刀在每一个\(C_j(j>i)\)右端点之前即可完全切断所有区间,\(r_{i+1}=\min_{i+1 \le j \le m} r_j\),因此右端点为\(r_{i+1}-1\)

所以满足题意的第二刀位置为\([last,r_{i+1})\),这个条件下已经满足了\(last \le r_{i+1}\),同时空区间的计算并不影响答案。

我们整理一下总贡献(在贡献统计中我们都使用\(r_i-len+1\)代替\(l_i\),因为\(SAM\)统计的是后缀):

\[res_{2.1}=\sum_{i=1}^{m-1} (r_{i+1}-r_i)(r_{i+1}-last)\\ =\sum_{i=1}^{m-1} (r_{i+1}-r_i)r_{i+1}-last\sum_{i=1}^{m-1} (r_{i+1}-r_i) \]

\(2.2\)有一刀切完了所有区间(这里的统计并不注重顺序关系)

首先考虑满足条件的一刀位置,容易得出是\([last,first)\)

继续考虑,两刀都在\([last,first)\)区间内,因为这个区间在\(B\)的左端点之后,在\(2.1\)中没有被统计,因此贡献为\({first-last \choose 2}\)

另一刀不在该区间内,在\(2.1\)的统计中,这一段被统计过一次,那时我们统计的是\([last-len+1,last)\)为第一刀,那么\([last,first)\)显然是满足条件的第二刀,我们不能再统计这里的贡献。

因此另一刀可以在\([1,last-len+1)\)\([last,n)\)中,贡献为\((first-last)(n-len)\)

整理贡献:

\[res_{2.2}={first-last \choose 2}+(first-last)(n-len) \]

综合\(2.1\)\(2.2\),得到贡献为:

\[res_2=\sum_{i=1}^{m-1} (r_{i+1}-r_i)r_{i+1}-last\sum_{i=1}^{m-1} (r_{i+1}-r_i)+{first-last \choose 2}+(first-last)(n-len) \]

\(3.\)\(A,B\)不相交

那么有一刀切完了所有区间是不可能出现的。

我们可以先把\(2.1\)的式子\(Copy\)过来,看看哪里出了问题。

\[res_{2.1}=\sum_{i=1}^{m-1} (r_{i+1}-r_i)(r_{i+1}-last) \]

\(2.1\)中,第二刀位置为\([last,r_{i+1})\)

第一个问题,存在\(r_{i+1} \le last\)的问题,而这种情况显然是无解的,我们不能把负数加入答案。

第二个问题,由于\(first\)不再位于所有串左端点的右侧,也就是说区间\(1 \sim i\)已经被切断并不成立,那么如果第一刀位于\([l_i,l_{i+1})\),我们必须让区间\(1 \sim i\)已经被切断,也就是这一刀位置\(<first\)

对于问题一,我们有了限制\(r_{i+1}>last\),即线段树中\(last\)的后继开始的节点。

对于问题二,发现这个限制是对于每一个位置而言的,因此\([l_i,l_{i+1})\)可能不再等价,有一部分需要舍去。

但是不等价区间只有一个,因此我们可以直接找到该区间\(C_k\)(线段树中\(first+len-1\)的前驱),那一段区间的第一刀为\([l_k,first)\),第二刀为\([last,r_{k+1})\)

那一段区间的贡献为:

\[(first-r_k+len-1)(r_{k+1}-last)[r_{k+1}>last] \]

统计贡献:

\[res_3=(first-r_k+len-1)(r_{k+1}-last)[r_{k+1}>last]+(\sum_{i=1}^{m-1} (r_{i+1}-r_i)r_{i+1}-last\sum_{i=1}^{m-1} (r_{i+1}-r_i))[last<r_{i+1}<first+len-1] \]

来总的式子吧!

\[res= \begin{cases} 0 \quad ( \exists C_k \subseteq (first,last))\\ \\ \sum_{i=1}^{m-1} (r_{i+1}-r_i)r_{i+1}-last\sum_{i=1}^{m-1} (r_{i+1}-r_i)+{first-last \choose 2}+(first-last)(n-len) \\( \nexists C_k \subseteq (first,last),A \cap B \ne \emptyset)\\ \\ (first-r_k+len-1)(r_{k+1}-last)[r_{k+1}>last]+(\sum_{i=1}^{m-1} (r_{i+1}-r_i)r_{i+1}-last\sum_{i=1}^{m-1} (r_{i+1}-r_i))[last<r_{i+1}<first+len-1] \\( \nexists C_k \subseteq (first,last),A \cap B = \emptyset) \end{cases} \\ ans={n-1 \choose 2}-res \]

观察式子,可以发现\(first,last,k\)等量可以在权值线段树上查找最值、前驱后继得到,我们需要计算的是两个量。

\[\sum (r_{i+1}-r_i)r_{i+1}\\ \sum (r_{i+1}-r_i) \]

由于权值线段树并不是维护区间的,因此我们合并两个区间必须将左区间的\(\max\)和右区间的\(\min\)合并,也就是需要额外维护两个标记。

\(AC\)代码(伟大的成就):

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define N 100005
#define ll long long
using namespace std;
const int INF=1000000007;
int n,q,l,r,k,d,first,last,Len;
char str[N];
int tot=1,lst=1,tr[N << 1][10],pre[N << 1],len[N << 1];
int ed[N];
int f[N << 1][22];
ll ans=0;
void ins(int c)
{
    int p=lst,np;
    lst=np=++tot;
    len[np]=len[p]+1;
    for (;p && !tr[p][c];p=pre[p])
        tr[p][c]=np;
    if (!p)
        pre[np]=1; else
        {
            int q=tr[p][c];
            if (len[p]+1==len[q])
                pre[np]=q; else
                {
                    int g=++tot;
                    memcpy(tr[g],tr[q],sizeof(tr[q]));
                    len[g]=len[p]+1,pre[g]=pre[q];
                    for (;p && tr[p][c]==q;p=pre[p])
                        tr[p][c]=g;
                    pre[np]=pre[q]=g;
                }
        }
}
struct edge
{
    int nxt,v;
    edge (int Nxt=0,int V=0)
    {
        nxt=Nxt,v=V;
    }
}e[N << 1];
int tt,fr[N << 1];
void add(int x,int y)
{
    ++tt;
    e[tt]=edge(fr[x],y),fr[x]=tt;
}
int Cnt=0,rt[N << 1];
#define ls(p) t[p].lc
#define rs(p) t[p].rc
#define s(p) t[p].S
#define muls(p) t[p].Muls
#define cnt(p) t[p].ct
#define mx(p) t[p].Mx
#define mn(p) t[p].Mn
struct node
{
    int lc,rc;
    int ct,Mx=0,Mn=INF;
    ll S=0,Muls=0;
}t[N << 6];
void update(int p)
{
    cnt(p)=cnt(ls(p))+cnt(rs(p));
    mx(p)=max(mx(ls(p)),mx(rs(p)));
    mn(p)=min(mn(ls(p)),mn(rs(p)));
    s(p)=s(ls(p))+s(rs(p));
    muls(p)=muls(ls(p))+muls(rs(p));
    if (!mx(ls(p)) || mn(rs(p))==INF)
        return;
    s(p)+=mn(rs(p))-mx(ls(p));
    muls(p)+=(ll)(mn(rs(p))-mx(ls(p)))*mn(rs(p));
}
void modify(int &p,int l,int r,int x)
{
    if (!p)
        p=++Cnt;
    if (l==r)
    {
        ++cnt(p);
        mx(p)=mn(p)=x;
        return;
    }
    int mid=(l+r) >> 1;
    if (x<=mid)
        modify(ls(p),l,mid,x); else
        modify(rs(p),mid+1,r,x);
    update(p);
}
int combine(int x,int y,int l,int r)
{
    if (!x || !y)
        return x|y;
    int p=++Cnt;
    if (l==r)
    {
        cnt(p)=cnt(x)+cnt(y);
        mx(p)=max(mx(x),mx(y));
        mn(p)=min(mn(x),mn(y));
        return p;
    }
    int mid=(l+r) >> 1;
    ls(p)=combine(ls(x),ls(y),l,mid);
    rs(p)=combine(rs(x),rs(y),mid+1,r);
    update(p);
    return p;
}
struct cnode
{
    int Mx=0,Mn=INF;
    ll S=0,Muls=0;
    cnode (int MX=0,int MN=INF,ll St=0,ll MULS=0)
    {
        Mx=MX,Mn=MN,S=St,Muls=MULS;
    }
};
cnode combine(cnode x,cnode y)
{
    cnode z;
    z.Mx=max(x.Mx,y.Mx);
    z.Mn=min(x.Mn,y.Mn);
    z.S=x.S+y.S;
    z.Muls=x.Muls+y.Muls;
    if (!x.Mx || y.Mn==INF)
        return z;
    z.S+=y.Mn-x.Mx;
    z.Muls+=(ll)(y.Mn-x.Mx)*y.Mn;
    return z;
}
cnode calc(int p,int l,int r,int x,int y)
{
    if (!p)
        return cnode();
    if (l==x && r==y)
        return cnode(mx(p),mn(p),s(p),muls(p));
    int mid=(l+r) >> 1;
    if (y<=mid)
        return calc(ls(p),l,mid,x,y); else
    if (x>mid)
        return calc(rs(p),mid+1,r,x,y); else
        return combine(calc(ls(p),l,mid,x,mid),calc(rs(p),mid+1,r,mid+1,y));
}
int findmn(int p,int l,int r)
{
    if (!cnt(p))
        return -1;
    if (l==r)
        return l;
    int mid=(l+r) >> 1;
    if (cnt(ls(p)))
        return findmn(ls(p),l,mid); else
        return findmn(rs(p),mid+1,r);
}
int findmx(int p,int l,int r)
{
    if (!cnt(p))
        return -1;
    if (l==r)
        return l;
    int mid=(l+r) >> 1;
    if (cnt(rs(p)))
        return findmx(rs(p),mid+1,r); else
        return findmx(ls(p),l,mid);
}
int findpre(int p,int l,int r,int x)
{
    if (!cnt(p))
        return -1;
    if (l==r)
        return l;
    int mid=(l+r) >> 1;
    if (x-1<=mid)
        return findpre(ls(p),l,mid,x); else
        {
            int o=findpre(rs(p),mid+1,r,x);
            if (~o)
                return o; else
                return findmx(ls(p),l,mid);
        }
}
int findsucc(int p,int l,int r,int x)
{
    if (!cnt(p))
        return -1;
    if (l==r)
        return l;
    int mid=(l+r) >> 1;
    if (x+1>mid)
        return findsucc(rs(p),mid+1,r,x); else
        {
            int o=findsucc(ls(p),l,mid,x);
            if (~o)
                return o; else
                return findmn(rs(p),mid+1,r);
        }
}
void work(int u)
{
    for (int i=fr[u];i;i=e[i].nxt)
    {
        int v=e[i].v;
        f[v][0]=u;
        work(v);
        rt[u]=combine(rt[u],rt[v],1,n);
    }
}
ll C2(int x)
{
    return (ll)x*(x-1)/2;
}
void Task1()
{
    printf("%lld\n",ans);
}
void Task2()
{
    cnode res=calc(rt[d],1,n,1,n);
    ans-=res.Muls-(ll)last*res.S+C2(first-last)+(ll)(first-last)*(n-Len);
    printf("%lld\n",ans);
}
void Task3()
{
    cnode res;
    int sti=findsucc(rt[d],1,n,last);
    int edi=findpre(rt[d],1,n,first+Len-1);
    k=edi;
    if (sti!=-1 && edi!=-1 && sti<=edi)
    {
        sti=findpre(rt[d],1,n,sti);
        res=calc(rt[d],1,n,sti,edi);
        ans-=res.Muls-(ll)last*res.S;
    }
    if (k!=-1)
    {
        int succ=findsucc(rt[d],1,n,k);
        if (succ!=-1 && succ>last)
            ans-=(ll)(first-k+Len-1)*(succ-last);
    }
    printf("%lld\n",ans);
}
int main()
{
    scanf("%d%d",&n,&q);
    scanf("%s",str+1);
    for (int i=1;i<=n;++i)
        ins(str[i]-'0');
    for (int i=2;i<=tot;++i)
        add(pre[i],i);
    int st=1;
    for (int i=1;i<=n;++i)
    {
        st=tr[st][str[i]-'0'];
        ed[i]=st;
        modify(rt[st],1,n,i);
    }
    work(1);
    for (int j=1;j<=20;++j)
        for (int i=1;i<=tot;++i)
            f[i][j]=f[f[i][j-1]][j-1];
    while (q--)
    {
        scanf("%d%d",&l,&r);
        d=ed[r];
        for (int i=20;i>=0;--i)
            if (f[d][i] && r-len[pre[f[d][i]]]<l)
                d=f[d][i];
        if (r-len[pre[d]]<l)
            d=pre[d];
        Len=r-l+1;
        first=findmn(rt[d],1,n);
        last=findmx(rt[d],1,n)-Len+1;
        ans=C2(n-1);
        if (first==-1)
            Task1(); else
        if (first>=last)
            Task2(); else
            Task3();
    }
    return 0;
}

\(update:\)在该代码\(findpre,findsucc\)两个查找前驱后继的函数中调用了\(findmx,findmn\),其实不调用复杂度也是对的。

也就是下面的代码同样可行。

int findpre(int p,int l,int r,int x)
{
    if (!cnt(p))
        return -1;
    if (l==r)
        return l;
    int mid=(l+r) >> 1;
    if (x-1<=mid)
        return findpre(ls(p),l,mid,x); else
        {
            int o=findpre(rs(p),mid+1,r,x);
            if (~o)
                return o; else
                return findpre(ls(p),l,mid,x);
        }
}
int findsucc(int p,int l,int r,int x)
{
    if (!cnt(p))
        return -1;
    if (l==r)
        return l;
    int mid=(l+r) >> 1;
    if (x+1>mid)
        return findsucc(rs(p),mid+1,r,x); else
        {
            int o=findsucc(ls(p),l,mid,x);
            if (~o)
                return o; else
                return findsucc(rs(p),mid+1,r,x);
        }
}
posted @ 2020-11-19 21:10  GK0328  阅读(100)  评论(0编辑  收藏  举报