前言
巨水水水水的一道题,居然没做起,30分骗分完全在讽刺当时的自己好吗qwq...
题目
给你一个长度为N的正整数序列,如果一个连续的子序列,子序列的和能够被K整
除,那么就视此子序列合法,求原序列包括多少个合法的连续子序列?
对于一个长度为8的序列,K=4的情况:2, 1, 2, 1, 1, 2, 1, 2 。它的答案为6,子序列
是位置1->位置8,2->4,2->7,3->5,4->6,5->7。
Input
第一行:T,表示数据组数
对于每组数据:
第一行:2个数,K,N
第二行:N个数,表示这个序列
Output
共T行,每行一个数表示答案
Sample Input
2 7 3 1 2 3 4 8 2 1 2 1 1 2 1 2
Sample Output
0 6
数据范围
100%数据满足
1<=T<=20
1<=N<=50000
1<=K<=1000000
序列的每个数<=1000000000
30%数据满足
1<=T<=10
1<=N,K<=1000
分析
前缀和+“余数”思想+排列组合
1)预处理前缀和and各前缀和mod k 的个数
2)余数相同的前缀和数列相减得出——余数为0的子序列
3)排列组合一下:余数为 i 的前缀和数列个数设为cnt[ i ],它们两两组合可以有cnt[ i ]*( cnt[ i ]-1 )种情况,答案cnt[ 0 ]~cnt[ k-1 ]累加即可
有一个细节是cnt[ 0 ] (mod k 余数为0的前缀和数列个数)最后要单独加一次,或者其初值附为1,因为它自己就可以整除,不需要和别的前缀和数列相减
骗分代码(贴过来皮一下)
//已确认会超时orz...
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN=50000;
ll a[MAXN+5],pre[MAXN+5];
int k,n;
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int ans=0;
//int tot=0;
scanf("%d%d",&k,&n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
pre[i]=pre[i-1]+a[i];
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n-i+1;j++)//子序列开头位置
{
ll sum=pre[j+i-1]-pre[j-1];
if(sum%(ll)k==0)
ans++;
}
printf("%d\n",ans);
}
return 0;
}
AC代码
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN=5e4,MAXK=1e6;
ll a[MAXN+5],pre[MAXN+5],cnt[MAXK+5];
int k,n,ans;
void Init()
{
ans=0;
memset(cnt,0,sizeof(cnt));
cnt[0]=1;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
Init();
scanf("%d%d",&k,&n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
pre[i]=pre[i-1]+a[i];
cnt[pre[i]%k]++;//计算余数个数
}
for(int i=0;i<=k-1;i++)
if(cnt[i]>=1)
ans+=cnt[i]*(cnt[i]-1);
printf("%d\n",ans/2);
}
return 0;
}