某类线段树的复杂度分析

题目:http://codeforces.com/contest/610/problem/E
题意:给出一个长为n,n<=200000只含前k,k<=10个字母的ss,有m,m<=2w次操作,每次:
1 l r c 将[l,r]范围内的字符都改为c
2 s保证s是前k个字母的一个排列,询问最少将s循环都少次使得ss是s的子序列

分析:考虑询问,令dp[i]代表ss中前i个字符的答案,显然有

dp[i]=dp[i1]+(rk[ss[i]]>rk[ss[i1]]?0:1)

        再想一下就是只要统计出每种相邻字母出现的次数就可以计算答案了。
题解给出了一种 naive 的做法,就是线段树暴力存10*10的矩阵,每次修改利用线段树暴力维护即可,时间复杂度和空间复杂度都不忍指示直视。
        注意到我们可以直接维护一个全局的 rep[10][10] 来统计每种相邻数对的出现情况,每次修改操作的时候修改 rep[10][10] 即可,由于当一段区间所有数都相同时,是可以 O(1) 计算贡献的,因此对于修改操作,我们可以暴力找 [L,R] 内所有相同区间,这样就可以算出贡献,维护 rep 数组。由于区间修改的特殊性,对于本题,线段树可以只开一个数组。
        虽然这种做法数见不鲜,但是我想说的是复杂度的证明。由于单次操作的复杂度最坏是 O(n) ,如何证明均摊的时间复杂度是 O(logn) 单次的呢?首先 O(log2n) 应该是一个上界,假如把修改看成把区间内所有标记都删去,再新加入一个标记的过程,那么标记总数不会超过 nlog2n 。注意到当我们把一段区间修改成相同时,下次再找到这个区间中任意一个节点都会立即退出。因此我们不妨这样思考,首先我们给每个线段树节点投资 1元,那么我们的总钱数是 O(n) 的,接下去考虑每次修改,首先,由于访问到一个全相同节点将立刻导致退出,因此我们认为访问到全相同节点不产生任何消耗;相反,访问到任意一个非全相同 节点将消耗一枚金币。对于每次修改操作,对于还没访问到[L,R]区间内的节点我们消耗了一元,我们同时投资一元,由于线段树的单次访问到节点总数是 O(logn) 总投资就上升为 O(nlogn+n) ,从我们这种投资方式可以看出,当我们找到某个节点且他不是全相同节点时,即使我们暴力找所有子树下的全相同节点,也不会导致某个节点的投资额<0,由于时间复杂度不会超过我的投资总额,因此 O(nlogn) 将是一个复杂度上界

代码:

#include
using namespace std;
#define ls l,mid,x<<1
#define rs mid+1,r,x<<1|1
const int Maxn=200020;
int n,m,k;
char s[Maxn];
int a[Maxn<<2];
int rep[22][22],rk[22];
void push_up(int x)
{
    if(a[x<<1]==a[x<<1|1])a[x]=a[x<<1];
    else a[x]=-1;
}
void push_down(int x)
{
    if(a[x]!=-1)
    {
        a[x<<1]=a[x<<1|1]=a[x];
    }
}
void build(int l,int r,int x)
{
    if(l==r){a[x]=s[l]-'a';return;}
    a[x]=-1;
    int mid=(l+r)>>1;
    build(ls);build(rs);
    push_up(x);
}
int query(int tar,int l,int r,int x)
{
    if(l==r||a[x]!=-1)return a[x];
    int mid=(l+r)>>1;
    if(tar<=mid)return query(tar,ls);
    return query(tar,rs);
}
int tpc;//前一个字符,应该初始化为>10
void ask(int L,int R,int l,int r,int x)//注意边界
{
    int mid=(l+r)>>1;
    if(L<=l&&R>=r)
    {
        if(a[x]!=-1){rep[tpc][a[x]]--;rep[a[x]][a[x]]-=r-l;tpc=a[x];}
        else{ask(L,R,ls);ask(L,R,rs);}
        return;
    }
    push_down(x);
    if(L<=mid)ask(L,R,ls);
    if(R>mid)ask(L,R,rs);
}
void add(int L,int R,int w,int l,int r,int x)
{
    if(L<=l&&R>=r)
    {
        a[x]=w;
        return;
    }
    push_down(x);
    int mid=(l+r)>>1;
    if(L<=mid)add(L,R,w,ls);
    if(R>mid)add(L,R,w,rs);
    push_up(x);
}
int main()
{
    scanf("%d%d%d",&n,&m,&k);
    scanf("%s",s+1);
    for(int i=2;i<=n;i++)rep[s[i-1]-'a'][s[i]-'a']++;
    build(1,n,1);
    while(m--)
    {
        int ty,l,r;
        char ss[22];
        scanf("%d",&ty);
        if(ty==1)
        {
            scanf("%d%d%s",&l,&r,ss);
            int c=ss[0]-'a';
            tpc=20;
            ask(max(l-1,1),min(n,r+1),1,n,1);
            add(l,r,c,1,n,1);
            if(l>1){int t1=query(l-1,1,n,1);rep[t1][c]++;}
            if(rint t1=query(r+1,1,n,1);rep[c][t1]++;}
            rep[c][c]+=r-l;
        }
        else
        {
            scanf("%s",ss);
            for(int i=0;i'a']=i;
            int ans=1;
            for(int i=0;ifor(int j=0;jif(rk[i]>=rk[j])ans+=rep[i][j];
                }
            printf("%d\n",ans);
        }
    }
}

你可能感兴趣的:(cf,数据结构,复杂度分析)