E - ∙ (Bullet)
题意:
有 N 条不同的沙丁鱼,第 i 条沙丁鱼的 美味程度 和 香味程度 分别是 Ai 和 Bi。
在这些沙丁鱼中选择 一条 或者 多条 放入冷冻箱,但是必须保证沙丁鱼的选择是合格的。
(合格的定义:其中的任意两条沙丁鱼 i 和 j 都不满足 Ai × Aj + Bi × Bj = 0。)
问多少种选择沙丁鱼的方法(选择的沙丁鱼的集合相同,算同一种方法),答案对 1e9+7 取模。
数据范围:
1 ≤ N ≤ 2 ×
≤ Ai, Bi ≤
思路:
首先将公式化简下:Ai × Aj + Bi × Bj = 0 → Ai × Aj = −Bi ∗ Bj → 。
我们可以把 Ai / Bi 相同的分成一组,统计出属于每组鱼的数量,再把 Ai / Bi 和 −Bi/ Ai 的两组看成一对,这两组肯定是相对的。也就是说,这两组是不能同时选的,我们把每一对能选的方案数求出来后,将所有对的方案数相乘再减1(排除全部不选的情况)就是总方案数。
每一对的方案数如何计算呢——
首先预处理出该对中两个组中鱼的个数后,先算出每个组内部的方案数,假设第一组(值为 A / B )鱼的个数为 s1,那么该组内部方案数为 (相当于每个鱼都有选/不选两种选择)。同理求出另一组(值为 -B / A )的方案数。所以该对的方案数为 。
说明:
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的理解。