给定一个长度为N的数组A=[A1,A2, … AN],请你将A拆分成3段连续的子数组:
A1, A2, … Ap | Ap+1, Ap+2, … Aq | Aq+1,Aq+2, … AN
令
S1 = A1 + A2 + … Ap
S2 = Ap+1 + Ap+2 + … Aq
S3 = Aq+1 + Aq+2 + … AN
问有多少种划分方案可以使得S1, S2, S3两两相差不超过1.
N <= 100000, -1000000 <= Ai <= 1000000
例子:
A = [3, 2, 1, 0, 2]
3 | 2 | 1 0 2
3 | 2 1 | 0 2
3 | 2 1 0 | 2
基本思路:枚举分界点p和q的位置,计算S1、S2和S3;如果符合要求ans++
for p = 1 … N-2:
forq = p + 1 … N-1:
S1=S2=S3=0
for i = 1 … p {S1+=A[i]}
for i = p+1 … q {S2+=A[i]}
for i = q+1 … N {S3+=A[i]}
if |S1-S2|<=1 && |S2-S3|<=1 && |S3-S1| <=1:
ans++
优化思路:枚举分界点p和q的位置,利用前缀和计算S1、S2和S3;如果符合要求ans++
for p = 1 … N-2:
forq = p + 1 … N-1:
S1=s[p] – s[0]
S2=s[q] – s[p]
S3=s[N] – s[q]
if |S1-S2|<=1 && |S2-S3|<=1 && |S3-S1| <=1:
ans++
优化思路的代码:#include
using namespace std;
const int maxn = 100005;
typedef long long ll;
int n;
ll sum[maxn];
int main()
{
map cnt;
cin>>n;
sum[0] = 0;
int tmp;
//下面求前缀和
for(int i =1;i <= n;i ++)
{
cin>>tmp;
sum[i] = sum[i-1] + tmp;
}
//下面枚举q
ll ans = 0;
ll s1,s2,s3;
for(int q = n;q > 2 ;q --) // 第3个数字 到最后一个数字
{
s3 = sum[n] - sum[q-1];
for(int p = 1;p <= q-2;p ++)
{
s1 = sum[p];
if(abs(s1- s3) <= 1)
{
s2 = sum[q-1] - sum[p];
if(abs(s1 - s2) <= 1 && abs(s2 - s3) <= 1)
ans ++;
}
}
}
cout<
但是这样的枚举的时间复杂度仍然在0(n^2),所以我们还要想更好的优化思路
试想 枚举p q 不行,那么我枚举一个q行不行呢 枚举q 得到s3 然后利用前缀和
判断s1和和s3的关系 如果满足 则计算剩下的s2 如果s2仍然满足 则这是一个符合的分割方法。 这样就把问题缩小了一个量级
还要注意数字的范围 最大的数肯定超过了int 所以这里我们要用longlong
#include
using namespace std;
const int maxn = 100005;
typedef long long ll;
int n;
ll sum[maxn];
int a[maxn];
int main()
{
map cnt;
cin>>n;
sum[0] = 0;
//录入数据
for(int i =1;i <= n;i ++)
cin>>a[i];
//下面计算前缀和
for(int i = 1 ;i <= n;i ++)
{
sum[i] = sum[i-1] + a[i];
if(!cnt.count(sum[i]))
cnt[sum[i]] = 0;
if(i < n)
cnt[sum[i]] ++; //和值尾sum[i] 的情况 +1
}
ll ans = 0;
ll s3 = 0;
for(int i = n;i > 1;i --)
{
if(i > 1)
cnt[sum[i-1]] --; //此时 刨去 s3 前面不要的那个前缀和 因为 s2的长度至少为1
s3 += a[i]; //s3 每向前推一次 加一次a[i] 即从最后第一项开始加
for(int t = s3-1; t <= s3+1;t ++)
{
if(abs(sum[n] - s3 - t - s3) <= 1 && abs(sum[n] - s3 - t - t) <= 1)
ans += cnt[t];
/*这里比较难理解,解释一下,
sum[n]就是所有总数的和,减去s3 就是s1和s2的和 再减去t(此时t充当s1)剩下的就是s2 再减去s3
即判断s2与s3的差值绝对值是否小于等于1 同理后面判断的是s1和s2 如果都满足就加上cnt[t]
为什么加cnt[t], 设想 s3为最后一个数字的和,那么剩下的n-1 为s1和s2的和 ,在剩下的数字里面
有cnt[t]种分割s1和s2的方法 都使得s1 s2 和s3 的差值在1以内 所以直接加上cnt[t]即可
这样优化之后的复杂度即为n*3 即O(n);
*/
}
}
cout<