给定一个序列,个数为n。再给出一系列w;对于每个w,求序列中,所有长度为w的连续子串中的权值和,子串权值为子串中不同数字的个数。
dp[i]表示w=i时所求的答案。dp[1]=n,这个很容易知道,dp[2]中的子串就是删去dp[1]中最后一个子串,再每个子串加上其之后的那个数,以此类推。
对于dp[i-1]推dp[i],加上的那部分:只有当这个数与它前面同值数最短距离大于等于i时才会加权值,否则会重复而不加。
所以可以推出递推式:dp[i]=dp[i-1]-lnc[i-1]+sum。
所以关键点有二:
1.求要删去的最后一个子串的权值,这个for循环一遍就可以,lnc[i]表示w=i的最后一个子串权值。
2.难的就是求加上一个数后所加的权值:令add[i]表示一个数与它前面值相同的数的最近距离,这也能循环一遍存下来。递推求解时,加上sum
如:
3 3 3 4 2 3 3 5
3 3 4 2 3 3 5
3 4 2 3 3 5
4 2 3 3 5
2 3 3 5
3 3 5
3 5
5
给出两组测试数据:
input
8
3 3 3 4 2 3 3 5
8
1 2 3 4 5 6 7 8
output
8 11 13 14 13 10 7 4
input
9
3 2 4 3 2 4 3 2 4
9
1 2 3 4 5 6 7 8 9
output
9 16 21 18 15 12 9 6 3
代码如下:
#include<bits/stdc++.h>
#define CPY(A,B)memcpy(A,B,sizeof(A))
typedef long long LL;
typedef unsigned long long uLL;
const int MOD=1e9+7;
const int INF=0x3f3f3f3f;
const LL INFF=0x3f3f3f3f3f3f3f3fLL;
const double EPS=1e-9;
const double OO=1e20;
const double PI=acos (-1.0);
int dx[]= {0,1,0,-1};
int dy[]= {1,0,-1,0};
int gcd (const LL &a,const LL &b) {return b==0?a:gcd (b,a%b);}
using namespace std;
const int maxn=1e6+50;
int num[maxn],lnc[maxn],cx[maxn],add[maxn];
LL dp[maxn];
int main() {
int n;
while (~scanf ("%d",&n) &&n) {
dp[1]=n;
for (int i=1; i<=n; ++i) {scanf ("%d",&num[i]);}
memset (cx,0,sizeof (cx) );
lnc[1]=1; cx[num[n]]=1;
//第一组最后一列个数为一,cx[num[n]]=1的意思就是num[n]这个数存在
for (int i=2; i<=n; i++) {
if (cx[num[n-i+1]]==0) {
cx[num[n-i+1]]=1;//第一次遇到该数字
lnc[i]=lnc[i-1]+1;//数字不重复,所以计数加一
} else { lnc[i]=lnc[i-1]; }//忽略重复的数字
}//存下每一回最后一组的不重复数字个数
memset (cx,0,sizeof (cx) );
memset (add,0,sizeof (add) );
for (int i=1; i<=n; i++) {
add[i-cx[num[i]]]++;
//数字和在其前面一样的数字的最近距离
cx[num[i]]=i;
//记录当前数字的位置
}
int sum=n;
for (int i=2; i<=n; i++) {
dp[i]=dp[i-1]-lnc[i-1];
//减掉最后一组
sum-=add[i-1];
dp[i]+=sum;
//加上新增加的部分
}
int Q; scanf ("%d",&Q);
while (Q--) {
int w; scanf ("%d",&w);
printf ("%I64d\n",dp[w]);
}
}
return 0;
}