20241120 校内模拟赛 T3 题解

题目描述

给定一个数列 AAA,数列的元素取值范围为 [1,m][1,m][1,m]

请计算有多少个非空子区间满足以下条件:该区间内每个元素的出现次数都相同(没有出现的元素视为出现 000 次)。

例如,当 m=3m=3m=3 时,[1,2,3][1,2,3][1,2,3][1,1,3,2,3,2][1,1,3,2,3,2][1,1,3,2,3,2] 是满足条件的区间,而 [1,2,2,3][1,2,2,3][1,2,2,3][1,1,3,3][1,1,3,3][1,1,3,3] 不满足条件。

请计算数列 AAA 的满足条件的非空子区间数量。

输入格式

包含多组测试数据,第一行一个正整数 TTT 代表数据组数。

每组数据两行,第一行两个整数 n,mn,mn,m,代表序列长度和元素值域。

第二行 nnn 个整数,代表该序列,保证序列中的元素在 [1,m][1,m][1,m] 之间。

输出格式

TTT 行,每行一个整数,代表对应组数数据的序列的满足条件的非空子区间个数。

样例

样例输入 1

1
6 3
1 2 3 1 2 3

样例输出 1

5

样例解释 1

[1,3],[2,4],[3,5],[4,6],[1,6][1,3],[2,4],[3,5],[4,6],[1,6][1,3],[2,4],[3,5],[4,6],[1,6]555555 个。

样例输入 2

1
10 4
1 1 2 4 3 2 4 3 2 1

样例输出 2

3

样例解释 2

[2,5],[1,8],[7,10][2,5],[1,8],[7,10][2,5],[1,8],[7,10]333333 个。

数据范围与提示

对于 100100%100 的数据, 1≤T≤5,1≤n≤106,1≤m≤n1≤T≤5,1≤n≤106,1≤m≤n1T5,1n106,1mn

测试点nnnmmm
111≤50≤5050≤50≤5050
222≤300≤300300≤300≤300300
333≤3000≤30003000≤3000≤30003000
444≤7000≤70007000≤7000≤70007000
5,6,75,6,75,6,7≤7×104≤7 × 10^47×104≤300≤300300
888≤3×105≤3×10^53×105≤3×105≤3×10^53×105
9,109,109,10≤106≤10^6106≤106≤10^6106

解法说明

首先让我们考虑一下暴力的做法。记 fi,jf_{i,j}fi,j 为截至第 iii 项数字 jjj 出现的次数,显然对于区间 (l,r](l,r](l,r],当且仅当 ∀i,j∈[1,m],fr,i−fl,i=fr,j−fl,j\forall i,j \in [1,m],f_{r,i}-f_{l,i}=f_{r,j}-f_{l,j}i,j[1,m],fr,ifl,i=fr,jfl,j 时,该区间合法。

接下来考虑优化。这事实上等价于对 fif_ififjf_jfj差分数组,当二者差分数组相同时,该区间合法。此时原问题转化为维护每个位置 fff 的差分数组的集合,寻找匹配数。可以使用类似异或哈希的技巧,求出每个位置 fff 的差分数组的哈希值,从而快速求出答案。如此复杂度已足够优秀,可以拿到满分。

但从上文的异或哈希角度出发,还能得到一个更简单的做法。用梅森旋转生成 m−1m-1m1 个数,分别作为 A1A_1A1Am−1A_{m-1}Am1 的哈希值,记其和的相反数AmA_mAm 的哈希值。令 idiid_iidi 表示 iii 的哈希值,记 fi=∑j=1iidAjf_i=\sum_{j=1}^i id_{A_j}fi=j=1iidAj当所有数出现次数相同时,其哈希值之和应为 000。即对于区间 (l,r](l,r](l,r],当且仅当 fl=frf_l=f_rfl=fr 时,该区间合法。故 fff 中的相同元素的对数即为原问题的答案。

通过代码

#include<bits/stdc++.h>

#define int long long//记得开long long
#define ull unsigned long long
const int N=1e6+10;

namespace IO{
    inline int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); return x*f; }
    inline void write(int x){ if(x>9) write(x/10); putchar(x%10+'0'); }
}using namespace IO;//快读快写

namespace code{
    int n,m,ans,id[N];
    ull a[N],sum;
    std::mt19937_64 random(time(0));//用梅森旋转算法生成高质量的伪随机数序列

    void solve(){
        n=read(),m=read(),ans=0,sum=0;//多测不清空,亲人两行泪
        for(int i=1;i<m;++i) id[i]=random(),sum+=id[i];//为第 1 到 m-1 项赋哈希值
        id[m]=-sum;//第 m 项的哈希值为前 m-1 项的哈希值的和的相反数
        for(int i=1;i<=n;++i) a[i]=a[i-1]+id[read()];//a[i] 表示第 1 到 i 项的哈希值的和
        std::sort(a,a+n+1);//排序,方便计算
        for(int i=1,cnt=1;i<=n;++i,++cnt,ans+=cnt-1) if(a[i]!=a[i-1]) cnt=0;//累加答案
        write(ans),putchar('\n');
    }
}

signed main(){
    freopen("st.in","r",stdin);
    freopen("st.out","w",stdout);//不要忘了文件操作
    int T=read();
    while(T--) code::solve();
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值