题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4455
一,题意:
给定一个序列ai,序列中元素个数为n。再给定一个整数w,w表示的是字串长度。
要求你求出给定序列中所有长度为w的子串中不同元素个数之和。
二,解析:
该题我们主要用:dp+线段树。
1,在这里线段数组主要用于快速求和,使得dp递推效率更高。
2,dp[i]表示区间长度为i时的个数。求dp[i]
则从区间长度i-1到i,要减去长度为i-1了最后一个i-1子区间,因为该区间无法扩展到长度为i的区间。
然后再其他的长度为i-1的区间后面加一个数就可以变成长度为i的区间,而被增加的数应该为:
a[i],a[i+1],,,,a[n]。所以我们主要的任务就是判断所增加的节点是否在原来的子串中出现过。
若这些数字在前面的字串子串中出现过则该子串中不同数子的个数不变,即不用增加。
若我们令插入后不用增加数的的个数为key,记last[i]表示最后一个区间长度为i的不同数的个数。
则:dp[i]=dp[i-1]+(n-i+1)-key-last[i],
对于last[i] 我们很容易求得,所以我们主要的任务是求解key。这里我们用到线段树快速求得key;
2,求key:
我们建立一个线段树,str[i] ,i表示的是当前结点到在他前面与他相等节点的最短距离,
记录的是有这样距离的当前结点的个数。但是这里我们还要处理一个问题就是当我们
我在需要考虑该节点是要消除前面所插入的距离,避免对后面产生影响。
三:代码:
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
using namespace std;
typedef long long LL;
const int Max =1000010;
int N,M;
int a[Max];
int str[Max];//树状数组(下标表示距离)
int Hash[Max];
int last[Max];//last[i]记录最后面的长度为i的子链的不同数的个数
LL dp[Max];//dp[i]表示区间长度为w=i时总和
int bit(int x)
{//树状数组每次移动的位数
return x&(-x);
}
void add(int x,int data)
{//在树状数组第i个位置增加data
while(x<=N)
{
str[x]+=data;
x+=bit(x);
}
}
LL sum(int x)
{//求树状树状求和
LL key=0;
while(x)
{
key+=str[x];
x=x-bit(x);
}
return key;
}
int main()
{
while(scanf("%d",&N)!=EOF&&N)
{
memset(Hash,-1,sizeof(Hash));
memset(str,0,sizeof(str));
for(int i=1;i<=N;i++)
{
scanf("%d",&a[i]);
if(Hash[a[i]]!=-1)
{
add(i-Hash[a[i]]+1,1);//在两个相同值最短距离处加1
add(i+1,-1);//i+1位置减一
//当你考虑区间所插入点没有i节点时我们要消除他前面所插入的距离
}
Hash[a[i]]=i;//a[i]最后一次出现的位置
}
memset(Hash,-1,sizeof(Hash));
last[0]=0;
for(int i=N;i>0;i--)
{
last[N-i+1]=last[N-i];
if(Hash[a[i]]==-1)
last[N-i+1]++;
Hash[a[i]]=i;
}
dp[1]=N;
for(int i=2;i<=N;i++)
dp[i]=dp[i-1]+(N-i+1)-sum(i)-last[i-1];
scanf("%d",&M);
for(int i=0;i<M;i++)
{
int k;
scanf("%d",&k);
printf("%lld\n",dp[k]);
}
}
return 0;
}