AtCoder - ABC 168 - E(数学推理)

博客解析了一道关于沙丁鱼选择的问题,其中涉及将沙丁鱼根据美味程度和香味程度划分合格组,并计算选择的合格方法总数。通过化简条件,将沙丁鱼分为不同组并计算每组的方案数,再考虑组间的关系求解。重点在于理解公式简化、分组计数和快速幂运算。
摘要由CSDN通过智能技术生成

 E - ∙ (Bullet)

题意:

有 N 条不同的沙丁鱼,第 i 条沙丁鱼的 美味程度 和 香味程度 分别是 Ai 和 Bi。
在这些沙丁鱼中选择 一条 或者 多条 放入冷冻箱,但是必须保证沙丁鱼的选择是合格的。
(合格的定义:其中的任意两条沙丁鱼 i 和 j 都不满足 Ai × Aj + Bi × Bj = 0。)

问多少种选择沙丁鱼的方法(选择的沙丁鱼的集合相同,算同一种方法),答案对 1e9+7 取模。

数据范围:

1 ≤ N ≤ 2 × 10^{5}

-10^{18} ≤ Ai​, Bi​ ≤ 10^{18}

思路:

参考博客

首先将公式化简下:Ai × Aj + Bi × Bj = 0 → Ai × Aj = −Bi ∗ Bj → \frac{A_{i}}{B_{i}} = -\frac{B_{j}}{A_{j}}  。
我们可以把 Ai / Bi 相同的分成一组,统计出属于每组鱼的数量,再把 Ai / Bi 和 −Bi/  Ai 的两组看成一对,这两组肯定是相对的。也就是说,这两组是不能同时选的,我们把每一对能选的方案数求出来后,将所有对的方案数相乘再减1(排除全部不选的情况)就是总方案数。

每一对的方案数如何计算呢——
首先预处理出该对中两个组中鱼的个数后,先算出每个组内部的方案数,假设第一组(值为 A / B )鱼的个数为 s1,那么该组内部方案数为 2^{s1} (相当于每个鱼都有选/不选两种选择)。同理求出另一组(值为 -B / A )的方案数。所以该对的方案数为 2^{s1}+2^{s2}-1

说明:
1. 因为其中的两组是不能同时选的,所以是 + 而不是 ×;
2. 因为在统计 s1 被选的时候,s2 一定是不选的;同理,在统计 s2 被选的时候,s1 一定是不选的;所以不需要考虑各自组都不取的这 2 种多算的情况;又因为 2 组一条鱼都不选也是 1 种合格的情况,所以又要加上 1 种情况,所以是最后需要 -2 + 1 = -1。
比如:s1=s2=2,总共有7种:2个(1,0),2个(0,1),1个(2,0),1个(0,2),最后算上(0,0)。

实现:

1. 用 mp<PII,int> 记录值为 a / b 的组中鱼个数。PII 存的是化简后的 a / b 的分子和分母。通过求出原分子分母的最大公因数 d 后,让分子分母都除以 d 来化简。注意 a,b 的范围,可能为 0 或负数——(1)如果a=b=0,d=0,取了它以后,别的都不能取,所以需要把每条这样的鱼单独归为一组单独放,只要加上这种鱼的个数s即可;(2)如果是 a / b 的值负数,为了我们之后在mp中找值为 -b / a 更方便,所以我们统一将存入的为负数的 a / b 的负号放在分母的位置,即 PII 的 second 位置。也就是说你所存入的(a,b)一定是第一个数为正,第二个数为负。

2. 遍历每一组,在遍历时用快速幂算出这一组的方案数 d 后,在 mp 中找与之相对的组,即存入的 PII 为(b,-a)的组(这里也是有负号位置的统一处理的)。再让 d 加上该组的方案后 -1 即可。得出一对的方案数。

3. 算出所有对的方案数的乘积后,最后-1+s(-1减的是所有鱼都不选的情况)即为最终答案。

Code:

参考代码

#include<bits/stdc++.h>
using namespace std;

const double PI = 4.0 * atan(1.0);
//#define PI 3.14159265358979
#define x first
#define y second
#define int long long
#define IOS ios::sync_with_stdio(false);cin.tie(0);

const int dx[] = { 1,-1,0,0 }, dy[] = { 0,0,1,-1 };
const int N = 100010, INF = 0x3f3f3f3f, mod = 1e9 + 7;
typedef pair<int, int>PII;

int n;
map<PII, int>mp;

//快速幂
int qmi(int a, int k)
{
	int res = 1;	
	while (k)
	{
		if (k & 1)res = res * a % mod;
		a = a * a % mod;
		k >>= 1;
	}
	return res;
}

void solve()
{
	cin >> n;
	int s = 0;
	for (int i = 0; i < n; i++)
	{
		int a, b;
		cin >> a >> b;
		int d = __gcd(a, b);								//计算两数的最大公因数,方便求a/b

		if (d == 0)											//d=0说明a,b都为0,只能单独为一组放,用s记录这样的鱼的个数
		{
			s++;
			continue;
		}
		a /= d, b /= d;										//得出化简后的a/b的分子分母
		if (a < 0)a = -a, b = -b;							//为了使得所存的分子,分母负数的位置统一,所以将负数的负号放在分母(b)的位置
		mp[{a, b}]++;										//累计ai/bi为该值的鱼的个数
	}

	int ans = 1;
	for (auto it : mp)										//单独遍历每一组(a/b不同的组)
	{
		if (it.y == 0)continue;

		int a = it.x.x, b = it.x.y;							//摘出该组a/b的分子a,分母b
		int d = qmi(2, mp[{a, b}]);							//计算该组内部可选的方案数,该组鱼的个数mp[{a,b}],个数为2^p[{a,b}]

		b = -b;												//与之相对的组,分母为-b
		if (b < 0)a = -a, b = -b;							//同样为了负数位置的一致性,如果是负数,就将负号赋给分母a
		if (mp.count({ b,a }))
		{
			d = (d + qmi(2, mp[{b, a}]) - 1) % mod;			//记录与之相对的组的内部种类数。-1=-2+1:-2是先去掉两组中各自拥有的都不选的情况,在算上的+1是两组都不选的情况
			mp[{b, a}] = 0;
		}
		ans = ans * d % mod;								//每一对的个数的乘积
	}
	ans = (ans - 1 + s + mod) % mod;						//减1是减去所有于都不选的情况
	cout << ans << endl;
}

signed main()
{
	IOS;

	//int t;
	int t = 1;
	//cin >> t;
	while (t--)
	{
		solve();
	}

	return 0;
}

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
吐槽:题目注意点:

1.整体思路就是将n个鱼根据ai/bi先分成几对,每一对分别算,每一对里的两个组再分别计算,再合并。
2.最后还包括一些细节处理。比如用map存对时负号位置的统一化。还有其中的+1,-1的理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值