噗叽组小朋友如何写出 NOIP T3?
本题解仅使用普及组知识点:搜索、剪枝、差分。
首先题意大家都懂。
我们不难发现,无论怎么交换差分,相邻两项的差值组成的集合是一样的。
对于这些差值,显然差值小的尽量接近平均值、尽量放中间,其他差值,从小到大放两边。
假设平均值为
基于这样的贪心,我们可以从小到大依次枚举每个差值放在最小差值的左边还是右边,最后依次递推出序列
代码如下:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e4 + 5;
int n, ans = 1e18, a[N], d[N], q[2 * N];
// dfs:尝试把差分放到左边或右边
void dfs(int step, int l, int r)
{
if (step >= n) // 放完了
{
int sum = a[1], t = a[1] * a[1] * n;
for (int i = 2; i <= n; i++)
{
a[i] = a[i - 1] + q[l + i - 2]; // 当前数 = 前一个数 + 对应的差分
sum += a[i]; // 累加总和
t += a[i] * a[i] * n; // 累加平方和
}
t -= sum * sum; // 减去总和的平方,得到真正的方差计算值
ans = min(ans, t);
return;
}
q[l - 1] = d[step]; // 放左边
dfs(step + 1, l - 1, r);
q[r + 1] = d[step]; // 放右边
dfs(step + 1, l, r + 1);
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i < n; i++) d[i] = a[i + 1] - a[i];
sort(d + 1, d + n);
q[N] = d[1]; // 把最小的差分放在最中间作为起点
dfs(2, N, N); // 从第 2 个差分开始,往左右两边填数
cout << ans << "\n";
return 0;
}
接下来,我们充分发挥人类智慧。
我们不难注意到,如数字很多但数值范围很小,差分序列范围更小。
假设有
如何利用这些性质进行剪枝?
-
差值为
0 时,放左边和放右边的效果是一样的,我们统一放左边,这样就只需要爆搜差值不为0 的部分,时间复杂度由\mathcal{O}(n \times 2^n) 降低到了\mathcal{O}(n \times 2^m) 。 -
对于差值
d ,如果有k 个,它们放左边和放右边是顺序无关的,我们可以指定顺序,先放左边,再放右边,避免重复。这样每一种差值的爆搜复杂度都由\mathcal{O}(2^k) 降低到了\mathcal{O}(k) 。
在爆搜基础上,加上这两个剪枝,即可通过本题。
::::info[时间复杂度] 以下内容来自 DeepSeek:
该代码的时间复杂度为:
这里
若
-
-
- 每个叶子需要递推构造整个数列并计算方差,耗时
O(n) 。
::::
代码如下:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e4 + 5;
int n, ans = 1e18, a[N], d[N], q[2 * N];
// dfs:尝试把差分放到左边或右边
void dfs(int step, int l, int r)
{
if (step >= n) // 放完了
{
int sum = a[1], t = a[1] * a[1] * n;
for (int i = 2; i <= n; i++)
{
a[i] = a[i - 1] + q[l + i - 2]; // 当前数 = 前一个数 + 对应的差分
sum += a[i]; // 累加总和
t += a[i] * a[i] * n; // 累加平方和
}
t -= sum * sum; // 减去总和的平方,得到真正的方差计算值
ans = min(ans, t);
return;
}
// 剪枝 1:如果差分为 0,放左放右结果一样,只搜一次即可
if (!d[step]) q[l - 1] = d[step], dfs(step + 1, l - 1, r);
else
{
// 剪枝 2:优先放左边,如果和右边边界值不同才搜,避免重复的对称状态
if (d[step] != q[r]) q[l - 1] = d[step], dfs(step + 1, l - 1, r);
q[r + 1] = d[step]; // 放右边
dfs(step + 1, l, r + 1);
}
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i < n; i++) d[i] = a[i + 1] - a[i];
sort(d + 1, d + n);
q[N] = d[1]; // 把最小的差分放在最中间作为起点
dfs(2, N, N); // 从第 2 个差分开始,往左右两边填数
cout << ans << "\n";
return 0;
}