题意:给出一个长度为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;
}