K倍区间 异或和 前缀和
一.
题目描述:给定一个长度为 N 的数列,A1,A2,…AN,如果其中一段连续的子序列 Ai,Ai+1,…Aj 之和是 K 的倍数,我们就称这个区间 [i,j] 是 K 倍区间。你能求出数列中总共有多少个 K 倍区间吗?
结论:求区间和,可以通过前缀和来求出。sum[r]−sum[l−1]就是区间[l,r]的和。如果区间[l,r]的和是k的倍数则有(sum[r]−sum[l−1])%k==0,即sum[r]%k==sum[r]%k。因此,我们可以得到一个结论,对前缀和取模之后,两个相等的前缀和就能组成一个k倍区间。
思考:有了这个结论之后,我们就可以使用两层for循环来计数k倍区间的个数,但是由于数据比较大,我们不能这样做。那么我们能不能在计算前缀和的过程中同时来统计k倍区间的个数呢?当然可以。我们可以用一个数组cnt,规定cnt[i]表示当前位置之前,前缀和取模后等于i的个数,以后每出现一次前缀和(取模后)和它相等,那么k倍区间就加上cnt[sum[i]],然后cnt[sum[i]]++。这样似乎不容易理解,我们用样例举个例子。
对于数列 1 2 3 4 5,k = 2
对前1个数的和模k后为1,在此之前有0个前缀和取模后为1,总个数+0
对前2个数的和模k后为1,在此之前有1个前缀和取模后为1,总个数+1
对前3个数的和模k后为0,在此之前有0个前缀和取模后为0, 总个数+0
对前4个数的和模k后为0,在此之前有1个前缀和取模后为0,总个数+1
对前5个数的和模k后为1,在此之前有2个前缀和取模后为1,总个数+2
但是我们还忽略了一点,就是我们这样做我们少计算了区间[0,i][0,i]构成的k倍区间,其个数为cnt[0]。
代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef unsigned long long ull; #define sc(n) scanf("%d",&n) #define print(n) printf("%d\n",n) #define endl "\n"; #define ios ios::sync_with_stdio(false);cin.tie(0); cout.tie(0) const int N=1000010,P=131; const int MOD = 1e9 + 7; const int INF=1e9; const double eps=1e-5; int n,m,k; int a[N],b[N],sum[N],res[N]; int main(){ sc(n); sc(k); ll ans=0; sum[0]=0; for(int i=1;i<=n;i++){ cin>>m; sum[i]=(sum[i-1]+m)%k; ans+=res[sum[i]];//ans答案爆long long ;之前有出现取模后相同的答案就加上 res[sum[i]]++; } cout<<ans+res[0]<<endl; return 0; } /* */
二
和上面类似:
#include <bits/stdc++.h> using namespace std; typedef long long ll; vector<ll> q[100005];//前缀和模k为i的值存在q[i] int n,k,z; ll a[100005]; int solove(ll x){ int p = x % k; int res = upper_bound(q[p].begin(),q[p].end(), x-z) - q[p].begin();//找到x-q[i]>=z的个数 q[p].push_back(x); return res; } int main(){ scanf("%d%d%d",&n,&k,&z); q[0].push_back(0);//模k等于0算自己 for(int i = 1 ; i <= n ; ++i){ scanf("%lld",a+i); a[i] += a[i-1]; } int ans = 0; for(int i = 1 ; i <= n ; ++i){ ans += solove(a[i]); } printf("%d\n",ans); }
杂题题解 文章被收录于专栏
看菜鸡做的水题