HDU 4455 Substrings(递推+优化)

该题是一道极好的递推+优化的题目(有很多人喜欢把递推归为DP,其实递推不具备DP的特点)。

因为对于每一个序列都要多次查询,每次查询长度为w的连续子序列中不同元素之和。  一开始确实没想到用递推,经验太少吧。  如果我们用d[i]表示长度为i的答案,那么由于子序列是连续的,所以d[i]和d[i-1]是有很大关系的。 首先很容易看出,d[i]的子序列比d[i-1]少一个,很容易发现,少了d[i-1]的最后一个序列,所以我们可以用O(n)的时间算出长度为i的最后一个序列中独一无二元素个数last[i] 。   然后从区间i-1长度增加到i增加了n-i+1个数增加的这些数为a[i],a[i+1],a[i+2]....a[n],则增加的这些数表示区间长度为i且以a[i,i+1,i+2....n]结尾,那么这些数中有多少是不能增加的呢?比如a[1]~a[i-1]存在a[i],以a[i]结尾的区间长度为i的区间就不用增加a[i]这个数。

接下来就比较巧妙了,我们可以处理出来所有元素距离前面一个相同元素的距离,那么距离大于区间长度的数才能加进来。由于不需要动态修改值,并不用树状数组,只需要一个数组就行。 挺卡内存的,只好重复利用了数组vis 。

细节参见代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1000000+10;
int T,n,m,x,q,last[maxn],len[maxn];
ll a[maxn],vis[maxn];
void init_last() {
    memset(vis,0,sizeof(vis));
    for(int i=n;i>=1;i--) {
        if(!vis[a[i]]) {
            vis[a[i]]++;
            last[n-i+1] = last[n-i] + 1;
        }
        else last[n-i+1] = last[n-i];
    }
}
void init_len() {
    memset(vis,-1,sizeof(vis));
    for(int i=1;i<=n;i++) {
        if(vis[a[i]] == -1) {
            vis[a[i]] = i;
            len[i] = i+1;
        }
        else {
            len[i] = i - vis[a[i]] + 1;
            vis[a[i]] = i;
        }
    }
}
void init_bit() {
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++) {
        vis[len[i]]++;
    }
    for(int i=1;i<=n+1;i++) {
        vis[i] += vis[i-1];
    }
}
int main() {
    while(~scanf("%d",&n)&&n) {
        for(int i=1;i<=n;i++) scanf("%I64d",&a[i]);
        init_last();
        init_len();
        init_bit();
        a[1] = n;
        for(int i=2;i<=n;i++) {
            a[i] = a[i-1] - last[i-1] + vis[n+1] - vis[i];
        }
        scanf("%d",&q);
        while(q--) {
            scanf("%d",&x);
            printf("%I64d\n",a[x]);
        }
    }
    return 0;
}


你可能感兴趣的:(HDU,递推,ACM-ICPC)