这个题目是2012年的区域赛的题目,是一个典型的dp题目,但是状态定义,即状态转移方程真是不太好想。自己一直在往多维的状态上想,但是都觉得不合理。后来,我也是看了大神的题解才明白的。自己就试着写下自己对这个题目的理解。题目的意思比较好懂:就是给定一个长度为n的序列。然后让你求出它的所有长度为m的连续子序列中,每个序列里不同元素的个数,并将求其和。
题目思路:状态定义为dp[i]表示子序列的长度为i时各个子序列中不同元素个数的和。状态转移方程比较难想,也不太好理解好实现,具体看代码注释吧。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAX 1000010
#define ll __int64
using namespace std;
ll dp[MAX]; //dp[i]表示子序列长度为i时的结果为多少
int a[MAX],f[MAX],c[MAX],s[MAX];
//f[i]记录a[i]最近一次出现的位置
//c[i]记录子序列的长度为i时,整个序列有多少对相同的元素
//s[i]表示最后i个元素中有多少不同的元素
int main(){
int n,q;
while (scanf("%d",&n)&& n != 0) {
memset(s,0,sizeof(s));
memset(f,0,sizeof(f));
memset(c,0,sizeof(c));
for(int i = 1; i<=n; i++) { //输入数据并求c[]数组,注意初始化的时候将每个元素第一次出现的位置定为0,这样做有助于后面的计算。
scanf("%d",&a[i]);
c[i-f[a[i]]]++;
f[a[i]] = i;
}
memset(f,0,sizeof(f));
f[a[n]] = 1; //求s[]数组
s[1] = 1;
for (int i = 2; i<=n; i++){
if (f[a[n-i+1]] == 0){
f[a[n-i+1]] = 1;
s[i] = s[i-1] + 1;
}
else{
s[i] = s[i-1];
}
}
dp[1] = n;
int tmp = n; //注意tmp的值得更新方法,根据是:长度为i的子序列中相同元素的对数 = 长度小于i的子序列中相同元素的对数加上子序列长度为i时相同元素的对数。
for (int i = 2; i<=n; i++){
dp[i] = dp[i-1] - s[i-1]; //从长度为i-1变到长都为i为“失去”最后i-1个元素
tmp -= c[i-1];
dp[i] += tmp;
}
scanf("%d",&q);
int t;
while (q--){
scanf("%d",&t);
printf("%I64d\n",dp[t]);
}
}
return 0;
}