题解:P2119 [NOIP 2016 普及组] 魔法阵

· · 题解

solution

一道数学推导优化枚举的题。

容易写出暴力枚举,考虑通过缩小枚举范围优化枚举。

知道 X_a<X_b<X_c<X_dX_b-X_a=2(X_d-X_c)<(X_c-X_b) \div 3

设一个参数 k=X_d-X_c,对原式变形得 X_b-X_a=2k6k<X_c-X_b,即 X_b=X_a+2kX_c>X_b+6k

对于每个 kX_aX_bX_cX_d 的距离都是定值,而 X_bX_c 的距离是个区间。

首先要了解这点。若 X_{c_1}<X_{c_2}X_{d_1}<X_{d_2},并且 X_{c_1}-X_b > 6k,即 X_{c_1}X_{d_1} 可以组成魔法阵,那么 X_{c_2}-X_b > 6k,也就是 X_{c_2}X_{d_2} 一定可以组成魔法阵。

类似的,若 X_{a_1}<X_{a_2}X_{b_1}<X_{b_2},并且 X_c-X_{b_2} > 6k,即 X_{a_1}X_{b_1} 可以组成魔法阵,那么 X_c-X_{b_1} > 6k,也就是 X_{a_2}X_{b_2} 一定可以组成魔法阵。

我们从后往前枚举 X_a,记 S_{x_i} 表示 x_i 出现次数,乘法原理得当前可以作为 X_a 的个数为 S_{X_b} \times S_{X_c} \times S_{X_d},用一个后缀和维护 S_{X_c} \times S_{X_d} 变枚举边算,这样就可以 O(n^2) 做了。

从前往后枚举 X_d,维护前缀和,同样可以算出贡献。

那就得到了做法,先枚举 k,再分别枚举 X_aX_d 的范围计算贡献。

$X_d$ 的范围是 $9k+2$ 到 $n$。当 $y=1$ 且 $X_a=1$ 时 $X_d$ 最小。 这道题还是有点抽象,不太好描述,可以结合代码体会。 ## code ```cpp #include <bits/stdc++.h> using namespace std; int n, m, A[50005][4], x[50005], num[50005]; int main () { cin >> n >> m; for (int i=1; i<=m; ++i) cin >> x[i], num[x[i]]++; for (int k=1, a, b, c, d, s; 9*k<=n; ++k) { for (a=n-9*k-1, s=0; a>=1; --a) // 枚举a,确定bcd b=a+2*k, c=b+6*k+1, d=c+k, s+=num[c]*num[d], A[a][0]+=num[b]*s, A[b][1]+=num[a]*s; for (d=9*k+2, s=0; d<=n; ++d) // 枚举d,确定abc a=d-9*k-1, b=a+2*k, c=d-k, s+=num[a]*num[b], A[c][2]+=num[d]*s, A[d][3]+=num[c]*s; } for (int i=1; i<=m; ++i, cout << '\n') for (int j=0; j<4; ++j) cout << A[x[i]][j] << ' '; return 0; } ```