洛谷P2119 [NOIP 2016 普及组] 魔法阵【题解】【前缀和优化】

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)XbXa=2×(XdXc)

Xb−Xa<(Xc−Xb)÷3X_b - X_a < (X_c - X_b) \div 3XbXa<(XcXb)÷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^41n1.5×1041≤m≤4×1041 \le m \le 4 \times 10^41m4×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 = tXdXc=t

那么可以得到:Xb−Xa=2tX_b - X_a = 2tXbXa=2t

将该式代入三式中,可得 6t<Xc−Xb6t < X_c - X_b6t<XcXb

显然 ∃k∈N∗\exists k \in N^*kN 满足 6t+k=Xc−Xb6t + k = X_c - X_b6t+k=XcXb

画个图来理解。

A ----2t2t2t---- B ------6t+k6t+k6t+k------ C —ttt— D

可以发现, t,kt,kt,k 确定,DDD 确定,四元组的个数即可求。

考虑枚举 tttDDD ,显然 A≥1,D≤nA \ge 1,D \le nA1,Dn ,所以易得 1≤t≤n−191 \le t \le \frac{n-1}{9}1t9n1

考虑 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=D9tk,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 sumcnt[D]×sumDDD 出现的次数为 ∑cnt[C]×sum\sum cnt[C] \times sumcnt[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;
} 

挺好的一道数学推导题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值