Fork me on GitHub

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

 

posted @ 2014-11-08 21:34  whatbeg  阅读(167)  评论(0编辑  收藏  举报