码蹄杯2026省赛第三轮/ccpc吉林省赛部分赛后补题
码蹄杯 2026.5.24
刺累自尽毕 MC0574
给定 \(a_1\) ~ \(a_n\) ,有 \(m\) 次询问,每次询问一个整数 \(L\) ,问所有长度为 \(L\) 的连续子段的数字种类数之和是多少。
例如序列 \(a=[2,2,3,1]\) ,所有长度为2的连续子段为 \([2,2]\) ,\([2,3]\) ,\([3,1]\) ,数字种类数分别为1,2,2。问询的答案为1+2+2=5。
其中 \(1\le n,m\le10^6\) ,\(1\le a_i,L \le n\)。
考虑每一个位置上的数字 \(a_i\) 对答案的贡献。在同一个子段内,出现了两个及以上的相同数字,只认为第一个数字是对答案有效贡献的。
例如 \(a=[2,2,3,1]\) ,问询 \(L=2\)时,子段 \([2,2]\) 只认为第一个2有贡献。这样我们在统计每一个位置 \(i\) 上的数字时,能影响到的区间 \([l,r]\) 的 \(l\) 范围应该设定为 \([pre[i]+1,i]\) ,其中 \(pre[i]\) 表示位置 \(i\) 的前一个相同数字的位置;而 \(r\) 的范围应该设定为 \([i,n]\) 。
现在考察位置 \(i\) ,假设左端点所在的区间长度为3,右端点的区间长度为7,考虑其对 \(L=1,2,3...\) 分别的影响。
\(L=1\) ,只能取区间 \([i]\) ;
\(L=2\) ,取区间 \([i-1,i]\) , \([i,i+1]\)
\(L=3\) ,取区间 \([i-2,i-1,i]\) ,\([i-1,i,i+1]\) ,\([i,i+1,i+2]\)
\(L=4\) ,取区间 \([i-2,i-1,i,i+1]\) ,\([i-1,i,i+1,i+2]\) ,\([i,i+1,i+2,i+3]\)
\(......\)
设 \(a\) 为左右端点区间长度较小值, \(b\) 为左右端点区间长度较大值。
不难看出,当 \(L\le a\) 时,答案贡献数等于 \(L\);
当 \(a\le L \le b\) 时,答案贡献数为 \(a\);
当 \(b\le L \le a+b\) 时,答案贡献数为 \(a+b-L\)。
答案贡献数的形状是一个梯形。
可以考虑用二阶差分 \(O(n)\) 的维护每一个位置的贡献。
例子里的 \(a_i\)对答案数组 \(ans[1,9]\) 的影响 \(1,2,3,3,3,3,3,2,1\) 一次差分可以拆解为形如 \(1,1,1,0,0,0,0,-1,-1,-1\) 的形式,再一次拆解可以变成 \(1,0,0,-1,0,0,0,-1,0,0,1\) 的形式。
实现如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 1e6+25;
inline ll read(){
ll w=1,q=0;char ch=' ';
while(w!='-' && (ch<'0' || ch>'9')) ch=getchar();
if(ch=='0') w=-1,ch=getchar();
while(ch<='9' && ch>='0') q=q*10+ch-'0',ch=getchar();
return w*q;
}
ll n,m,a[MAXN];
ll mem[MAXN],pre[MAXN],d1[MAXN],d2[MAXN],ans[MAXN];
signed main(){
n=read(),m=read();
for(int i=1; i<=n; i++) a[i]=read();
for(int i=1; i<=n; i++){
pre[i] = mem[a[i]];
mem[a[i]] = i;
}
for(int i=1; i<=n; i++){
int a = i-pre[i];
int b = n-i+1;
int p = min(a,b),q = max(a,b);
d1[1] += 1;
d1[p+1] -= 1;
d1[q+1] -= 1;
d1[p+q+1] += 1;
}
for(int i=1; i<=n; i++){
d2[i] = d2[i-1]+d1[i];
}
for(int i=1; i<=n; i++){
d2[i] += d2[i-1];
}
for(int i=1; i<=m; i++){
cout<<d2[read()]<<endl;
}
return 0;
}

浙公网安备 33010602011771号