前言:
膜拜Orz
点赞 投币 收藏(三连键)
前置知识:
前缀和与差分
想必大家都学过 不知道滴 出门右转 百度欢迎您-
「USACO2016JAN」Subsequences Summing to Sevens
题意:
给n个数 找 和能被7 整除的 最长区间
思路:
第一眼 暴力 遍历数组+比大小**(2022年11月的想法)**
结果全部TLE 所以一直搁置
4个月后的第二眼 啊~ 前缀和 然后解决
前缀和公式:
s [ j ] = ∑ i = 1 j a [ i ] s[j]=\sum^{j}_{i=1}{a[i]} s[j]=∑i=1ja[i]
先了解一个概念 :
如果一段连续的数的和是7的倍数,那么这段连续数列结尾位置的值一定和开头前一位置的值相同
即
如果区间[x,y]的所有数字和是7的倍数 那么有
s[y]%7==s[x-1]%7
具体看下面的例子
给出数组a x=2 y=5
2 5 3 4 2
我们要求第二位到第五位的和是否是7的倍数 就看 第一位 的数值 是否等于 我们要求区间的最后一位 第一位的2等于最后一位的2 所以这段连续的数的和 能被7整除
- 再比如
给出数组a x=2 y=5
4 3 2 5 1
我们要求第二位到第五位的和是否是7的倍数 看第二位的前一位等于4 第五位等于1 4不等于1 所以该数列无法被整除
所以 我们只需要求数列的前缀和 前缀和模7的余数 余数第一次出现的位置 余数最后一次出现的位置与第一次出现位置的差(答案)就可以了
先定义一个数组 存前缀和%7的余数第一次出现的位置
只需要开长度为8的数组就够(记得初始化)
int f[8]=0;//(7的余数只有0 1 2 3 4 5 6 ,一共7种)(保险起见 开大一位)
数组赋初始值 -1(不赋值0是因为7的余数有0的可能(整除))
memset(f,-1,sizeof(f));
定义a数组 用来读入数据 s数组用来求a数组的前缀和 ans来记录区间长度
int a[maxn],s[maxn],ans;
先开**循环 **
for(int i=1;i<=n;i++)
在循环内 我们要完成4件事
读入数据 前缀和+模7 找相同余数 更新长度
第一件事:
读入数据
scanf("%d",&a[i]);
第二件事:
在完成前缀和的 同时 模7(取除以7的余数)
s[i]=(s[i-1]+a[i])%7;
第三件事:
找到有两个余数相同的位置 也就是这段连续数列最后位置的值和开头前一位置的值相同
如果这个余数没有出现过 那么我们记录新余数的位置
if(f[s[i]]==-1)
{
f[s[i]]=i;
}
如果出现过 也就是f[s[i]]!= -1
那么就完成最后一件事 对比区间长度 更新答案
ans=max(ans,i-f[s[i]]);
到此
恭喜你离AC不远了
but
坑点警告!
i-f[s[i]]
用当前次数(i) 减去相同余数第一次出现的的位置(f[s[i]])后才是真正的区间长度
最后输出答案
printf("%d\n",ans);
AC代码贴在下面
#include <bits/stdc++.h>
using namespace std;
const int maxn = 50000 + 10;
int a[maxn],s[maxn];
int f[8],n,ans;
int main() {
memset(f,-1,sizeof(f));
f[0]=0;
scanf("%d",&n);
for(int i=1; i<=n; i++) {
scanf("%d",&a[i]);
s[i]=(s[i-1]+a[i])%7;
if(f[s[i]]==-1) {
f[s[i]]=i;
} else {
ans=max(ans,i-f[s[i]]);
}
}
printf("%d\n",ans);
return 0;
}