1. 程式人生 > >【[TJOI2014]上升子序列】

【[TJOI2014]上升子序列】

這本質上是一個\(dp\)

如果沒有"兩個上升子序列相同,那麼只需要計算一次"這一個性質,那麼就很好做了,我們用\(dp[i]\)表示以\(i\)結尾的上升子序列個數,那麼就有\(dp[i]=\sum_{j=1}^{i-1}dp[j]\)

這個暴力轉移是\(O(n^2)\)的,我們這裡可以直接用樹狀陣列來優化,於是就變成了\(O(nlogn)\)

同時由於數字可能非常大,所以需要離散化

之後再來考慮一下如何去重

首先重複的情況肯定是來自於一個之前已經出現過的數,而這個出現的數又將所有之前那個點算出來的答案又都加了一遍,這樣就會有重複的了

那我們怎麼去掉這些重複的情況呢

首先直接不考慮這個再次出現的數是肯定不對的,如果這個數和它之前出現的那個位置之間有一些比這個數小的的數,那麼這些就就沒有被計入答案,於是就錯了

但是我們可以對每一個數維護一個\(lastans[i]\),表示\(i\)這個數上次被計入答案的時候\(\sum_{j=1}^{i-1}dp[j]\)是多少,之後我們還是用樹狀陣列來查詢字首和,之後我們計入答案的應該就是這次查詢出來的答案減去\(lastans\),也就是表示新增的上升子序列的個數是多少,之後我們再把這個數加入樹狀陣列

程式碼

#include<iostream>
#include<cstring>
#include<cstdio>
#include<map>
#include<algorithm>
#define re register
#define lowbit(x) ((x)&(-(x)))
#define maxn 100005
#define LL long long
#define int long long
const LL mod=1e9+7;
std::map<LL,LL> ma;
LL c[maxn];
int n;
LL a[maxn],b[maxn];
LL lastans[maxn];
int f[maxn];
inline LL read()
{
    char c=getchar();
    LL x=0,r=1;
    while(c<'0'||c>'9') 
    {
        if(c=='-') r=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')
      x=(x<<3)+(x<<1)+c-48,c=getchar();
    return x*r;
}
inline void add(int x,LL v)
{
    for(re LL i=x;i<=n;i+=lowbit(i))
        c[i]=(c[i]+v)%mod;
}
inline LL query(LL x)
{
    LL ans=0;
    for(re LL i=x;i;i-=lowbit(i))
        ans=(ans+c[i])%mod;
    return ans;
}
signed main()
{
    n=read();
    for(re int i=1;i<=n;i++) a[i]=b[i]=read();
    std::sort(b+1,b+n+1);
    int tot=std::unique(b+1,b+1+n)-b-1;
    for(re int i=1;i<=tot;i++)
        ma[b[i]]=i;
    LL cnt=0;
    for(re int i=1;i<=n;i++)
    {
        int j=ma[a[i]];
        if(!f[j])
        {
            LL mid=query(j-1);
            cnt=(cnt+mid)%mod;
            add(j,mid+1);
            lastans[j]=mid;
            f[j]=1;
            continue;
        }
        LL mid=query(j-1);
        cnt=(cnt+mid-lastans[j]+mod)%mod;
        add(j,(mid-lastans[j]+2*mod)%mod);
        lastans[j]=mid;
    }
    std::cout<<cnt;
    return 0;
}