HDU 4455 Substrings --递推+树状数组优化

题意: 给一串数字,给q个查询,每次查询长度为w的所有子串中不同的数字个数之和为多少。

解法:先预处理出D[i]为: 每个值的左边和它相等的值的位置和它的位置的距离,如果左边没有与他相同的,设为n+8(看做无穷)。

考虑已知w=k的答案,推w = k+1时,这时每个区间都将增加一个数,即后n-k个数会增加到这些区间中,容易知道,如果区间内有该数,那么个数不会加1,,即D[i] > k时,结果++,即查询后n-k个数有多少个D[i] > k 的数即为要加上的数,然后最后面还会损失一个区间,损失的是不能增加一个数的那个区间,随着w的增加,该区间会向左边伸展,所以记录一下该区间有多少个不同的数即可。 求区间内有多少个D[i]>k可以用树状数组先求有多少个D[i]<=k(getsum(k)),然后区间长度减一下即可。

代码:

#include <iostream>

#include <cstdio>

#include <cstring>

#include <cstdlib>

#include <cmath>

#include <algorithm>

#define lll __int64

using namespace std;

#define N 1000107



int c[N],n,D[N],Last,vis[N],a[N],pos[N],L[N];

lll sum[N];



int lowbit(int x) { return x&-x; }

void modify(int x,int val)

{

    while(x <= n+10)

    {

        c[x]+=val;

        x += lowbit(x);

    }

}



int getsum(int x)

{

    int res = 0;

    while(x > 0)

    {

        res += c[x];

        x -= lowbit(x);

    }

    return res;

}



int main()

{

    int q,i,j,w;

    while(scanf("%d",&n)!=EOF && n)

    {

        for(i=1;i<=n;i++)

            scanf("%d",&a[i]);

        memset(pos,0,sizeof(pos));

        for(i=1;i<=n;i++)

        {

            int x = a[i];

            D[i] = i-pos[x];

            if(D[i] == i) D[i] = n+8;

            pos[x] = i;

        }

        memset(c,0,sizeof(c));

        memset(vis,0,sizeof(vis));

        for(i=2;i<=n;i++)

            modify(D[i],1);

        sum[1] = n;

        Last = 1;

        vis[a[n]] = 1;

        for(i=2;i<=n;i++)

        {

            sum[i] = sum[i-1]+(n-i+1-getsum(i-1))-Last;

            modify(D[i],-1);

            if(!vis[a[n-i+1]])

            {

                vis[a[n-i+1]] = 1;

                Last++;

            }

        }

        scanf("%d",&q);

        while(q--)

        {

            scanf("%d",&w);

            printf("%I64d\n",sum[w]);

        }

    }

    return 0;

}
View Code

 

你可能感兴趣的:(substring)