Luogu P12376「LAOI-12」Calculate 题解 [ 蓝 ] [ 贪心 ] [ 线性 DP ] [ 前缀和优化 ] [ 范德蒙德卷积 ]
Calculate:简单计数 DP。
先考虑 \(\sum_{i = 1}^{p - 1}(a_{i + 1} - a_i)^2\) 的最大值怎么求。利用调整法可以得到结论:每次走当前未选的最大值、最小值交替选一定最优。还有另一种理解方式:注意到两数之差的平方很像欧几里得距离,所以把 \(a_i\) 变成点 \((a_i, a_i)\),那么如果要让起点为 \(1\) 且走过的路径最长,则一定是 \(a_1\to a_n\to a_2\to a_{n - 1}\to a_3\to\cdots\) 的路径最优。
有了贪心结论,又因为是子序列,所以可以将原序列排序以方便计数。此时要我们统计的是这样反复横跳的路径的权值和。
容易设计出一个 DP:\(dp_i\) 表示以 \(i\) 为最左侧端点,且长度为偶数的路径个数;\(c_i\) 表示以 \(i\) 为最左侧端点,且长度为偶数的路径权值和。从左到右枚举到 \(i\) 时,转移如下:
- \(dp_{j}\overset{+}{\leftarrow} \sum_{k = j+1}^{i - 1}dp_k\)。
- \(c_j\overset{+}{\leftarrow} \sum_{k = j + 1}^{i - 1}c_k + \sum_{k = j+1}^{i - 1}dp_k\times (a_i - a_k)^2 + (\sum_{k = j+1}^{i - 1}dp_k)\times(a_i - a_j)^2\)。
直接转移是 \(O(n^3)\) 的,前缀和优化一下即可做到 \(O(n^2)\)。另一种做法是范德蒙德卷积加上一点数学推导,但是我不会。
#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi = pair<int, int>;
const int N = 5005;
const ll mod = 998244353;
int n;
ll a[N], dp[N], c[N], ans;
int main()
{
//freopen("sample.in", "r", stdin);
//freopen("sample.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for(int i = 1; i <= n; i++)
cin >> a[i];
sort(a + 1, a + n + 1);
for(int i = 1; i <= n; i++)
{
ll smdp = 0, smc = 0, smdp2 = 0;
for(int j = i - 1; j >= 1; j--)
{
ll oridp = dp[j], oric = c[j];
dp[j] = (dp[j] + smdp) % mod;
c[j] = (c[j] + smc + smdp2 + smdp * (a[i] - a[j]) % mod * (a[i] - a[j]) % mod) % mod;
smdp = (smdp + oridp) % mod;
smdp2 = (smdp2 + oridp * (a[i] - a[j]) % mod * (a[i] - a[j]) % mod) % mod;
smc = (smc + oric) % mod;
dp[j] = (dp[j] + 1) % mod;
c[j] = (c[j] + (a[i] - a[j]) * (a[i] - a[j]) % mod) % mod;
}
}
for(int i = 1; i <= n; i++)
ans = (ans + c[i]) % mod;
cout << ans;
return 0;
}

浙公网安备 33010602011771号