给一个长度不超过10^6的序列,再给一些询问,对每个询问k,输出原序列中,每个长度为k的连续子序列种不同数字的个数的和。一眼看上去一点思路都没有...看题名难免会往SA和KMP上想..结果都没有什么好想法....昨天听@qian99大神说了下思路,豁然开朗= =...
结合样例说一下吧,
长度为1 1 1 2 3 4 4 5
长度为2 11 12 23 34 44 45
112 123 234 344 445
1123 1234 2344 3445
11234 12344 23445
112344 123445
1123445
不难发现,一层一层推下来,发现下一层都是上一层除了最后一组的数,后面添加一个数,如果添加的这个数在这组没出现过,就加一。这样,下一层的答案都是由上一层的答案剪掉上一层最后一组不同的数字个数再加上添加之后新增加的不同的数字个数,即dp[i]=dp[i-1]+A-B; 而B的值明显可以递推一下预处理出来,A的话我的写法对每个位置记录一下上一次自己出现的位置pos[i],没有就是0.然后开一个数组c[i]表示每一位和他上次出现的位置不大于i的有多少种情况。因为从上往下,添加的数每次都少一个,所以在递推c[i]的时候对当前的第一个数要特判一下,据题写法看代码吧。
#include <iostream> #include <cstdio> #include <algorithm> #include <memory.h> #include <cmath> #include <queue> #include <stack> #include <map> using namespace std; typedef long long ll; const int maxn=1000000+1000; int n,m,p,q,k; int last[maxn]; int pos[maxn]; int a[maxn]; ll c[maxn]; int sub[maxn]; bool vis[maxn]; ll dp[maxn]; int main() { // freopen("in.txt","r",stdin); while(scanf("%d",&n) && n) { memset(last,0,sizeof last); memset(pos,0,sizeof pos); memset(c,0,sizeof c); for (int i=1; i<=n; i++) { scanf("%d",&a[i]); } for (int i=1; i<=n; i++) { pos[i]=last[a[i]]; last[a[i]]=i; } // for (int i=1; i<=n; i++) // printf("%d ",pos[i]); // printf("\n"); // for (int i=1; i<=n; i++) // printf("%d ",last[i]); // printf("\n"); for (int i=1; i<=n; i++) { if (!pos[i]) continue; c[i-pos[i]]++; } for (int i=2; i<=n; i++) { c[i]+=c[i-1]; if (pos[i]) c[i]-=1; } memset(vis,false,sizeof vis); memset(sub,0,sizeof sub); vis[a[n]]=true; sub[1]=1; for (int i=n-1; i>=1; i--) { sub[n-i+1]=sub[n-i]; if (!vis[a[i]]) sub[n-i+1]++,vis[a[i]]=true; } memset(dp,0,sizeof dp); dp[1]=(ll)n; for (int i=2; i<=n; i++) { dp[i]=dp[i-1]-(ll)sub[i-1]+(ll)(n-i+1-c[i-1]); } scanf("%d",&m); while(m--) { scanf("%d",&k); printf("%I64d\n",dp[k]); } } return 0; }