P2119 [NOIP 2016 普及组] 魔法阵
题意简述
给定正整数 n,mn,mn,m ,有 mmm 个魔术物品,魔术值为 Xi(Xi∈[1,n])X_i(X_i \in [1,n])Xi(Xi∈[1,n]) 。定义魔法阵为:含四个魔术值 Xa,Xb,Xc,XdX_a,X_b,X_c,X_dXa,Xb,Xc,Xd ,满足:
Xa<Xb<Xc<XdX_a < X_b < X_c < X_dXa<Xb<Xc<Xd
Xb−Xa=2×(Xd−Xc)X_b - X_a = 2 \times (X_d - X_c)Xb−Xa=2×(Xd−Xc)
Xb−Xa<(Xc−Xb)÷3X_b - X_a < (X_c - X_b) \div 3Xb−Xa<(Xc−Xb)÷3
则称四个魔术物品分别为 AAA 物品,BBB 物品,CCC 物品,DDD 物品。
求出每个魔术物品作为 A,B,C,DA,B,C,DA,B,C,D 的次数。
1≤n≤1.5×1041 \le n \le 1.5 \times 10^41≤n≤1.5×104 ,1≤m≤4×1041 \le m \le 4 \times 10^41≤m≤4×104 。
思路
暴力
枚举所有可能的 444 元组,O(1)O(1)O(1) 判定是否可行,复杂度为 O(Cm4)O(C_m^4)O(Cm4) ,预期得分 45pts45pts45pts。
可以稍微加一点剪枝优化。
有空把暴力打满看看能有多少分。
先 sortsortsort 一下,再搜,选满四个直接判定,统计答案。
正解
观察到值域不算特别大,考虑桶排。
记 cnt[Xi]cnt[X_i]cnt[Xi] 为魔法值 XiX_iXi 出现的次数。再观察题目要求满足的式子,不妨设 Xd−Xc=tX_d - X_c = tXd−Xc=t ,
那么可以得到:Xb−Xa=2tX_b - X_a = 2tXb−Xa=2t 。
将该式代入三式中,可得 6t<Xc−Xb6t < X_c - X_b6t<Xc−Xb 。
显然 ∃k∈N∗\exists k \in N^*∃k∈N∗ 满足 6t+k=Xc−Xb6t + k = X_c - X_b6t+k=Xc−Xb 。
画个图来理解。
A ----2t2t2t---- B ------6t+k6t+k6t+k------ C —ttt— D
可以发现, t,kt,kt,k 确定,DDD 确定,四元组的个数即可求。
考虑枚举 ttt 和 DDD ,显然 A≥1,D≤nA \ge 1,D \le nA≥1,D≤n ,所以易得 1≤t≤n−191 \le t \le \frac{n-1}{9}1≤t≤9n−1
考虑 ttt 固定时如何求解四元组。
当 AAA 最小且 kkk 最小时,DDD 有最小值 9t+29t + 29t+2 ,从小到大枚举 DDD 。
对于当前 DDD ,合法(可以构成魔法阵)的 A,BA,BA,B 需满足 A=D−9t−k,B=A+2tA = D - 9t - k,B = A + 2tA=D−9t−k,B=A+2t 。
观察到 kkk 越大 AAA 越小,所以所有比当前 AAA 更小的组合都合法。(因为 kkk 可以取任意正整数嘛,只需要 A,BA,BA,B 之间距离大小恒为 2t2t2t 即可)。
于是可以使用前缀和(枚举的过程从小到大,正好符合前缀和累加顺序)记录前面已有的合法 A,BA,BA,B 组合,即 sum=∑cnt[A]×cnt[B]sum = \sum cnt[A] \times cnt[B]sum=∑cnt[A]×cnt[B] 。
由乘法原理,得
当前 CCC 出现的次数为 ∑cnt[D]×sum\sum cnt[D] \times sum∑cnt[D]×sum ,DDD 出现的次数为 ∑cnt[C]×sum\sum cnt[C] \times sum∑cnt[C]×sum
C,DC,DC,D 已经求解, A,BA,BA,B 同理,只需枚举 AAA 即可。
AAA 确定时,kkk 越大,DDD 越大,同上可知比当前 DDD 更大得组合都合法(因为 kkk 可以取任意正整数,只需要 C,DC,DC,D 距离恒为 ttt 即可) 。
所以从大到小枚举 AAA ,用后缀和记录后面已有的合法 C,DC,DC,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]]++;
}
//枚举t
for(int t = 1;t * 9 + 1 <= n;t++){
int sum = 0;
//先从小到大枚举D
for(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;
}
挺好的一道数学推导题。

793

被折叠的 条评论
为什么被折叠?



