E - ∙ (Bullet)(组合数学)

传送门

思路:显然对于一组 ( a , b ) (a,b) (a,b)只有两种情况, p o s 1 : pos1: pos1:同号或者异号, p o s 2 : pos2: pos2 a , b a,b a,b存在0。

p o s 1 : pos1: pos1:

且要使 A i A j + B i B j = 0 → A i B i = − B j A j A_iA_j+B_iB_j=0\rightarrow \dfrac{A_i}{B_i}=\dfrac{-B_j}{A_j} AiAj+BiBj=0BiAi=AjBj。显然是同号与异号进行组合。

e p : i : ( a , b ) , j : ( b , − a ) → a b + b ( − a ) = 0 ep:i:(a,b),j:(b,-a)\rightarrow ab+b(-a)=0 ep:i:(a,b),j:(b,a)ab+b(a)=0

且我们只需考虑 ( a , b ) (a,b) (a,b)除去最大公因数 g c d gcd gcd的组合.

因为 a b = a g c d b g c d \dfrac{a}{b}=\dfrac{\dfrac{a}{gcd}}{\dfrac{b}{gcd}} ba=gcdbgcda

所以我们考虑储存同号的个数及对应异号的个数。(这里用 m a p map map实现即可)

对于当前组 p a i r i pair_i pairi,假设同号个数为 c n t 1 cnt_1 cnt1,异号个数为 c n t 2 cnt_2 cnt2,由于不能同时选同号和异号的,

显然我们可以只选同号或者只选异号的,这样有 2 c n t 1 + 2 c n t 2 2^{cnt_1}+2^{cnt_2} 2cnt1+2cnt2种情况。

2 c n t 1 2^{cnt_1} 2cnt1 2 c n t 2 2^{cnt_2} 2cnt2同时包含了当前组一个数不选的情况,所以要减去1.

即对于当前组的贡献为 p a i r i = 2 c n t 1 + 2 c n t 2 − 1 pair_i=2^{cnt_1}+2^{cnt_2}-1 pairi=2cnt1+2cnt21.

所以根据乘法原理有:

a n s = p a i r 1 × p a i r 2 ⋯ × p a i r n ans=pair_1\times pair_2\dots\times pair_n ans=pair1×pair2×pairn

p o s 2 : pos2: pos2:

1.显然当 a , b a,b a,b中只有一个 0 0 0时,等价于 ( 1 , 0 ) (1,0) (1,0) ( 0 , 1 ) (0,1) (0,1)进行组合,可以归为 p o s 1 pos1 pos1.

2.当 a = b = 0 a=b=0 a=b=0时,因为它们不能与其他数共存,只能单独存在。所以对于这样的数贡献为 c n t a = b = 0 cnt_{a=b=0} cnta=b=0

由于集合的非空性:所以还要减去所有组一个数都不选的情况

∴ a n s = p a i r 1 × p a i r 2 ⋯ × p a i r n + c n t a = b = 0 − 1 \therefore ans=pair_1\times pair_2\dots\times pair_n+cnt_{a=b=0}-1 ans=pair1×pair2×pairn+cnta=b=01

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)

AC代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+5,mod=1e9+7;
#define mst(a) memset(a,0,sizeof a)
int n,cnt;
ll ksm(ll a,ll n){
	ll ans=1;
	while(n){
		if(n&1) ans=ans*a%mod;
		a=a*a%mod;
		n>>=1;
	}
	return ans;
}
int main(){
	map<pair<ll,ll>,pair<int,int> >mp;
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		ll a,b;
		scanf("%lld%lld",&a,&b);
		if(!a&&!b){
			cnt++;
			continue;
		}
		ll g=__gcd(a,b);
		a/=g,b/=g;
		if(b<0) a=-a,b=-b;//保证同号的时候都为正,异号的时候b恒为正. 
		if(a<=0) mp[{b,-a}].second++;//该组同号对应的异号个数. 
		else mp[{a,b}].first++;	//该组同号个数 
	}
	ll ans=1;
	for(auto it:mp){
		ans=(ans*(mod+ksm(2,it.second.first)+ksm(2,it.second.second)-1)%mod)%mod;//防止负数要+mod 
	}
	printf("%lld\n",(mod+ans+cnt-1)%mod);
	return 0;
}