HDU 4455 Substrings DP 不同转相同

题意:给出一个长度为N的数组。给出长度w,对于所有长度w的子串,求出其中有多少个不同的数字。然后对所有的长度为w的子串,求其和。

思路:因为长度为i+1的子串可以用长度为i的子串在后面拼接一个数字得到,这样就想到了用dp了吧。

          这里最难处理的是如果如何对一个子串查找有几个不同的数字。我们将这个问题转化为它的相反的问题,对于子串中每个数,有几个相同的。好像很难处理吧。但是要注意到,我们会枚举长度。如果我们预处理得到:对于每个数字,离他最近的和他相同的数字的位置。那给定长度时,我们就可以判断是否有相同的数字。

          接下来,我们来推导dp方程。

          对于这道题,w=1的时候,就是N。

          对于i+1的长度,我们递推它和i的关系。首先,因为长度加1,原来长度为i的后缀得到的不同的数字将被减去(不会出现在长度为i+1)。然后,我们对其他的子串在拼接一个数字。因为长度给定,我们记录了左面离它最近的和他相同的位置。如果这个位置差大于长度,那这个数字将是个有效数字,会让整个结果+1.所以,我们暴力枚举加入的最后一个数即可。

         但是这样的话,计算发现,复杂度是O(N^2).不可行。必须优化。

         对于内层循环,我们发现,当前数字和左面离他最近的和他值相同的数字的差,是个定值,换句话说,设这个值为L,它只对 dp[1]-dp[ L-1]有贡献,对dp[L]之后将没有贡献。这样,我们就可以对这些距离求和。我们也可以发现,有用的不是数字本身,而是数字和离它左面的且数值相同的数字的距离。

         还有一个小的细节:如果求出后缀长度为i中有多少个不同的数字。因为终点给定,我们就可以暴力O(N)求出了。

         这样,总的复杂度为O(N);

代码如下:

#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

const int MAX = 1000100;

int N,Q,w;
int a[MAX];
int left[MAX];
int record[MAX];
int suffix[MAX];
bool vis[MAX];
long long len[MAX];
long long dp[MAX];

int main(void)
{
    //freopen("input.txt","r",stdin);
    while(scanf("%d", &N),N){
        memset(vis,0,sizeof(vis));
        memset(record,0,sizeof(record));
        memset(len,0,sizeof(len));
        for(int i = 1 ; i <= N; ++i){
            scanf("%d", &a[i]);
            left[i] = record[a[i]];
            record[a[i]] = i;
            len[i - left[i]]++;
        }
        int cnt = 0;
        for(int i = 1; i <= N; ++i){
            if(!vis[a[N - i + 1]]){
                vis[a[N - i + 1]] = true;
                cnt++;
            }
            suffix[i] = cnt;
        }
        long long sum = 0;
        for(int i = 2; i <= N; ++i)
            sum += len[i];
        dp[1] = N;
        for(int i = 2; i <= N; ++i){
            dp[i] = dp[i-1] - suffix[i-1];
            dp[i] += sum;
            sum -= len[i];
        }
        scanf("%d", &Q);
        while(Q--){
            scanf("%d", &w);
            printf("%I64d\n",dp[w]);
        }
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值