洛谷P2119 [NOIP 2016 普及组] 魔法阵【题解】【前缀和优化】
P2119 [NOIP 2016 普及组] 魔法阵
题意简述
给定正整数 n , m n,m n,m ,有 m m m 个魔术物品,魔术值为 X i ( X i ∈ [ 1 , n ] ) X_i(X_i \in [1,n]) Xi(Xi∈[1,n]) 。定义魔法阵为:含四个魔术值 X a , X b , X c , X d X_a,X_b,X_c,X_d Xa,Xb,Xc,Xd ,满足:
X a < X b < X c < X d X_a < X_b < X_c < X_d Xa<Xb<Xc<Xd
X b − X a = 2 × ( X d − X c ) X_b - X_a = 2 \times (X_d - X_c) Xb−Xa=2×(Xd−Xc)
X b − X a < ( X c − X b ) ÷ 3 X_b - X_a < (X_c - X_b) \div 3 Xb−Xa<(Xc−Xb)÷3
则称四个魔术物品分别为 A A A 物品, B B B 物品, C C C 物品, D D D 物品。
求出每个魔术物品作为 A , B , C , D A,B,C,D A,B,C,D 的次数。
1 ≤ n ≤ 1.5 × 1 0 4 1 \le n \le 1.5 \times 10^4 1≤n≤1.5×104 , 1 ≤ m ≤ 4 × 1 0 4 1 \le m \le 4 \times 10^4 1≤m≤4×104 。
思路
暴力
枚举所有可能的 4 4 4 元组, O ( 1 ) O(1) O(1) 判定是否可行,复杂度为 O ( C m 4 ) O(C_m^4) O(Cm4) ,预期得分 45 p t s 45pts 45pts。
可以稍微加一点剪枝优化。
有空把暴力打满看看能有多少分。
先 s o r t sort sort 一下,再搜,选满四个直接判定,统计答案。
正解
观察到值域不算特别大,考虑桶排。
记 c n t [ X i ] cnt[X_i] cnt[Xi] 为魔法值 X i X_i Xi 出现的次数。再观察题目要求满足的式子,不妨设 X d − X c = t X_d - X_c = t Xd−Xc=t ,
那么可以得到: X b − X a = 2 t X_b - X_a = 2t Xb−Xa=2t 。
将该式代入三式中,可得 6 t < X c − X b 6t < X_c - X_b 6t<Xc−Xb 。
显然 ∃ k ∈ N ∗ \exists k \in N^* ∃k∈N∗ 满足 6 t + k = X c − X b 6t + k = X_c - X_b 6t+k=Xc−Xb 。
画个图来理解。
A ---- 2 t 2t 2t---- B ------ 6 t + k 6t+k 6t+k------ C — t t t— D
可以发现, t , k t,k t,k 确定, D D D 确定,四元组的个数即可求。
考虑枚举 t t t 和 D D D ,显然 A ≥ 1 , D ≤ n A \ge 1,D \le n A≥1,D≤n ,所以易得 1 ≤ t ≤ n − 1 9 1 \le t \le \frac{n-1}{9} 1≤t≤9n−1
考虑 t t t 固定时如何求解四元组。
当 A A A 最小且 k k k 最小时, D D D 有最小值 9 t + 2 9t + 2 9t+2 ,从小到大枚举 D D D 。
对于当前 D D D ,合法(可以构成魔法阵)的 A , B A,B A,B 需满足 A = D − 9 t − k , B = A + 2 t A = D - 9t - k,B = A + 2t A=D−9t−k,B=A+2t 。
观察到 k k k 越大 A A A 越小,所以所有比当前 A A A 更小的组合都合法。(因为 k k k 可以取任意正整数嘛,只需要 A , B A,B A,B 之间距离大小恒为 2 t 2t 2t 即可)。
于是可以使用前缀和(枚举的过程从小到大,正好符合前缀和累加顺序)记录前面已有的合法 A , B A,B A,B 组合,即 s u m = ∑ c n t [ A ] × c n t [ B ] sum = \sum cnt[A] \times cnt[B] sum=∑cnt[A]×cnt[B] 。
由乘法原理,得
当前 C C C 出现的次数为 ∑ c n t [ D ] × s u m \sum cnt[D] \times sum ∑cnt[D]×sum , D D D 出现的次数为 ∑ c n t [ C ] × s u m \sum cnt[C] \times sum ∑cnt[C]×sum
C , D C,D C,D 已经求解, A , B A,B A,B 同理,只需枚举 A A A 即可。
A A A 确定时, k k k 越大, D D D 越大,同上可知比当前 D D D 更大得组合都合法(因为 k k k 可以取任意正整数,只需要 C , D C,D C,D 距离恒为 t t t 即可) 。
所以从大到小枚举 A A A ,用后缀和记录后面已有的合法 C , D C,D C,D 组合。
代码实现
#include <bits/stdc++.h>using namespace std;void read(int &res){int x = 0,w = 1;char ch = 0;while(ch < '0' || ch > '9'){if(ch == '-') w = -1;ch = getchar();}while(ch >= '0' && ch <= '9'){x = (x << 3) + (x << 1) + (ch - '0');ch = getchar();}res = x * w;
}int main(){int n,m;read(n);read(m);vector <int> x(m + 1);vector <int> cnt(n + 1,0);vector <int> a(n + 1,0);vector <int> b(n + 1,0);vector <int> c(n + 1,0);vector <int> d(n + 1,0);for(int i = 1;i <= m;i++){read(x[i]);cnt[x[i]]++; }//枚举tfor(int t = 1;t * 9 + 1 <= n;t++){int sum = 0;//先从小到大枚举Dfor(int D = 9 * t + 2;D <= n;D++){int A = D - 9 * t - 1;int B = A + 2 * t;int C = D - t;sum += cnt[A] * cnt[B];//前缀和优化c[C] += cnt[D] * sum;d[D] += cnt[C] * sum;} sum = 0;//再从大到小枚举A for(int A = n - 9 * t - 1;A;A--){int B = A + 2 * t;int C = B + 6 * t + 1;int D = C + t;sum += cnt[C] * cnt[D];a[A] += cnt[B] * sum;b[B] += cnt[A] * sum;}} for(int i = 1;i <= m;i++) cout << a[x[i]] << " " << b[x[i]] << " " << c[x[i]] << " " << d[x[i]] << endl;return 0;
}
挺好的一道数学推导题。