该题是一道极好的递推+优化的题目(有很多人喜欢把递推归为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; }