HDU 5738 Eureka
题目大意:
给出平面上的 $n$ 个点,每个点有唯一的标号(label),这 $n$ 个标号的集合记作 $S$,点可能重合。求满足下列条件的 $S$ 的子集 $T$ 的数目:
1. $|T|\ge 2$
2. $T$中的点共线
Solution:
只包含一种点的符合条件的子集 $T$ 的数目很容易计算,以下我们只考虑其中至少有两种不同(指坐标不同,下同)点的符合条件的子集$T$(以下简称子集)的数目。
考虑过两不同点 $u, v$ 的直线上的点,将这些点的集合记作 $P_{uv}$,考虑 $P_{uv}$ 的子集对答案的贡献 $C_{u,v}$ 。
设 $|P_{uv}|=k$,则 $C_{uv}=2^k-k-1-\text{single}(P_{uv})$,其中 $\text{single}(P_{uv})$ 指 $P_{uv}$ 的只包含一种点的子集 $T$ 的数目。
现在固定 $u$,计算一共能得到几个 $P_{uv}$。这可通过以 $u$ 为基点,对(去重后的)其他点作极角排序来做到。
以每个点为基点做一次极角排序,这样每个点集 $P_{uv}$ 都被重复计算了 $|P_{uv}|$ 次。
我们用 $f[k]$ 表示模为 $k$ 的点集 $P_k$ 出现的次数。这个次数被重复计算了$k$次,$P_k$出现的次数为$\frac{f[k]}{k}$
则所有$P_k$贡献的子集的数目为
\[\frac{f[k]}{k}(2^k-k-1)-\sum\limits_{P_k}\text{single}(P_k),\]
故总计数为
\[\sum_{k=2}^{n}{\frac{f[k]}{k} 2^k-(k+1)} -\sum_{k=2}^{n}{\sum\limits_{P_k}\text{single}(P_k)}\]
为了计算$\sum_{k=2}^{n}{\sum\limits_{P_k}\text{single}(P_k)}$,另外维护一个数组 $d$,$d[u]$ 表示 $u$ 在多少个点集 $P$ 中出现过。
从而有
\[\sum_{k=2}^{n}{\sum_{P_k}\text{single}(P_k)}=\sum_{u=1}^{m}d[u]*(2^{c[u]}-c[u]-1),\]
其中 $m$ 指去重后的点数,$c[u]$ 表示与 $u$ 重合的点数。
Implement:
#include <bits/stdc++.h> using namespace std; const int N(1e3+5); typedef long long LL; LL mod=1e9+7; struct point{ LL x, y; void read(){ scanf("%lld%lld", &x, &y); } point operator-(const point &b)const{ return {x-b.x, y-b.y}; } point operator-(){ return {-x, -y}; } bool operator==(const point &b)const{ return x==b.x && y==b.y; } bool operator<(const point &b)const{ return x==b.x?y<b.y:x<b.x; } void out(){ cout<<x<<' '<<y<<endl; } LL operator^(const point &p)const{ return x*p.y-y*p.x; } }a[N], slope[N]; bool cmp(const int &a, const int &b){ return (slope[a]^slope[b])<0; //error-prone } int cnt[N], b[N], dup[N]; LL num[N], p[N]={1}; int main(){ int T, n; for(int i=1; i<=1000; i++) p[i]=(p[i-1]<<1)%mod; for(scanf("%d", &T); T--; ){ scanf("%d", &n); for(int i=0; i<n; i++) a[i].read();
sort(a, a+n); for(int i=0, j=0, k=0; i<n; j++, k=i){ for(; i<n && a[i]==a[k]; i++); cnt[j]=i-k; } int e=unique(a, a+n)-a; memset(num, 0, sizeof(num)); memset(dup, 0, sizeof(dup)); for(int i=0; i<e; i++){ for(int j=0, k=0; j<e; j++){ if(i==j) continue; slope[j]=a[j]-a[i]; if(slope[j].x<0) slope[j]=-slope[j]; else if(slope[j].x==0 && slope[j].y<0) slope[j]=-slope[j]; b[k++]=j; } sort(b, b+e-1, cmp); for(int j=0, k=0, sum; j<e-1; k=j){ for(sum=0; j<e-1 && (slope[b[j]]^slope[b[k]])==0; sum+=cnt[b[j++]]); num[sum+cnt[i]]+=cnt[i]; //error-prone dup[i]++; } } LL _dup=0, res=0; for(int i=0; i<e; i++) _dup+=dup[i]*(p[cnt[i]]-cnt[i]-1+mod), _dup%=mod; for(int i=0; i<e; i++) res+=p[cnt[i]]-cnt[i]-1+mod, res%=mod; for(int i=2; i<=n; i++) num[i]/=i, res+=num[i]*(p[i]-i-1+mod), res%=mod; res+=mod-_dup, res%=mod; printf("%lld\n", res); } }
UPD
在以某个点为基点进行极角排序需要进行一个将待排序的向量规范化(formalization)的过程, 具体的说就是保证各向量满足:
x>0 或 x==0 && y>0 (待排序都是非零向量)
题解上给出的做法更好一些:
想将输入的点去重后,按照字典序排序, 然后从左到右扫, 当扫到第$i$个点$p_i$时我们统计由$p_i$及其右面的点构成的且一定包含$p_i$的共线子集的数目, 统计的方法和前面类似.
这样做的好处是
1. 向量自然规范化
2. 不重不漏
Implementation: