1. 程式人生 > >loj10121 與眾不同(ST演算法)(二分)

loj10121 與眾不同(ST演算法)(二分)

題目

A 是某公司的 CEO,每個月都會有員工把公司的盈利資料送給 A,A 是個與眾不同的怪人,A 不注重盈利還是虧本,而是喜歡研究「完美序列」:一段連續的序列滿足序列中的數互不相同。 A 想知道區間 [L,R][L,R][L,R] 之間最長的完美序列長度。

嘗試

一開始想的是用權值線段樹,這樣只要滿足min[l,r]=1那麼這段就是合法的。 又想了想讓每個合法區間對提問更新,並不太可行。

題解

ST表+二分 繞了一大圈,其實應該直接求以i為結尾的最大長度... 設其為f[i],為了方便遞推,再設一個st[i]表示以i結尾的最大長度的開頭,那麼有f[i]=i-s[i]+1。 st[i]的轉移有兩條限制:一是顯然不能比st[i-1]還前(小),二是不能到最近一次出現的a[i](a[i]為盈利價值)。用last[i]維護數字i最近的位置,所以st[i]=max(st[i-1],last[i]+1)。

考慮求答案吧,一段區間[ql,qr],那麼最優解的右端點一定在[ql,qr]之間。左端點呢,隨意對吧,沒有什麼要求。但還是有一點特別的,對於一個k,如果st[k]<ql而且st[k-1]<ql,那麼k一定比k-1優秀。 所以答案的來源只有兩種可能了,一是一個最大的k滿足st[k]<ql,二是從k+1(根據上面k的特點,有st[k+1]>=ql)開始一直到r,f的最大值,即ans=max(k-l+1, max_{k<i<=r}{f[i]})。 那麼對於那段區間最大值,用ST表維護就好了。k的值因為st遞增,所以二分可以得到。 還要注意一下k的細節,如果st[r]<l要特判一下,此時k會超出[l,r]的範圍。

程式碼

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=2e5+10,MAXA=1e6;
int bin[20],log[MAXN];

void pre_work()
{
    for(int i=0;i<=20;i++) bin[i]=1<<i;
    log[0]=-1;for(int i=1;i<MAXN;i++) log[i]=log[i>>1]+1;
}

int n,m;
int st[MAXN];
int last[MAXA*2+10];

int f[MAXN][20];
int query(int l,int r)
{
    int s=log[r-l+1];
    return max(f[l][s],f[r-bin[s]+1][s]);
}

int main()
{
    pre_work();
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        int x;scanf("%d",&x);x+=MAXA;
        st[i]=max(st[i-1],last[x]+1);
        f[i][0]=i-st[i]+1;//debug f[i][0]=st[i]
        last[x]=i;
    }
    for(int i=1;i<=19;i++)
        for(int j=1;j+bin[i]-1<=n;j++) f[j][i]=max(f[j][i-1],f[j+bin[i-1]][i-1]);//debug j<=n
    while(m--)
    {
        int l,r,k;
        scanf("%d%d",&l,&r);l++,r++;
        if(st[r]<l) k=r+1;
        else k=lower_bound(st+1,st+n+1,l)-st;
        int ans=0;
        if(k>l) ans=k-l;
        if(k<=r) ans=max(ans,query(k,r));
        printf("%d\n",ans);
    }
    return 0;
}