P10366 [PA2024] Bardzo Ulubiony Ciąg 题解
0. 前言
作为一道计数/优化枚举的题目,本题比 [NOIP2022] 种花 难不少,思维难度较大。
本题主要考察的知识点:前缀和、容斥、枚举。
1. 大致思路
(注:为了方便计算,接下来先抛去题目中
首先不考虑 “
接着考虑
第一类:三个区间均相同,设情况数为
第二类:两个区间相同,第三个区间不同,设情况数为
则最终答案为
2. 具体实现
先对数列
下文设第
计算 B:
由于三个区间全部相同,所以
所以只需要统计
for(int l = 1; l <= n; l++)
for(int r = l; r <= n; r++)
cnt[a[r] - a[l - 1] + mv]++;
B = cnt[mv];
(注:代码中的变量名等会在文章最后有解释,可先去看,再回来理解该段代码)。
计算 C:
不失一般性的,假设
则由此可得
所以只需要枚举和为
for(int l = 1; l <= n; l++){
for(int r = l; r <= n; r++){
int sum = a[r] - a[l - 1];
if(sum == 0) C += cnt[mv] - 1;
else C += cnt[-2 * sum + mv];
}
}
C *= 3;
Q1:当
A1:因为其中有 “三段区间均相同” 的一种情况,而该情况在
Q2:那为什么
A2:此时
Q3:为什么最后
A3:在开头我们假设了
计算 A:
本题中最难,也是最巧妙的地方(前方高能!)。
不难想到枚举前两个区间,再根据
这里给出教练 noip_1 的一种奇妙思路:
memset(cnt, 0, sizeof cnt);
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++)
for(int k = j; k <= n; k++)
cnt[a[k] - a[j - 1] - a[i - 1] + mv]++;
for(int j = 1; j <= n; j++)
for(int k = j; k <= n; k++)
A += cnt[-(a[k] - a[j - 1] + a[i]) + mv];
}
以下记三个区间分别为
回到题目本身:这三个区间要满足
现在懂了吧!上面的代码所做的工作就是:将等式右边的
回顾一下暴力做法:将
3. 代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 505, V = 4e7 + 5, mv = 2e7;
int n, a[N], cnt[V];
ll A, B, C, ans;
int main(){
cin >> n;
for(int i = 1; i <= n; i++)
cin >> a[i], a[i] += a[i - 1];
for(int l = 1; l <= n; l++)
for(int r = l; r <= n; r++)
cnt[a[r] - a[l - 1] + mv]++;
B = cnt[mv];
for(int l = 1; l <= n; l++){
for(int r = l; r <= n; r++){
int sum = a[r] - a[l - 1];
if(sum == 0) C += cnt[mv] - 1;
else C += cnt[-2 * sum + mv];
}
}
C *= 3;
memset(cnt, 0, sizeof cnt);
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++)
for(int k = j; k <= n; k++)
cnt[a[k] - a[j - 1] - a[i - 1] + mv]++;
for(int j = 1; j <= n; j++)
for(int k = j; k <= n; k++)
A += cnt[-(a[k] - a[j - 1] + a[i]) + mv];
}
ans = (A - B - C) / 6;
cout << ans;
return 0;
}
注:
-
cnt为计数数组。由于可能出现负数,所以在存入桶前,要加上一个大数(即代码中的mv)。 -
代码中为了方便,直接将
a数组作为了前缀和。 -
记得开
long long!
4. 后记
这里 是我整理的此类题型的题单,持续更新中,大家有好题也可以在评论区推荐给我。
如果您觉得这篇题解对您有帮助的话,请点个赞,感谢 qwq。