JZOJ5953. 【NOIP2018模拟11.5A组】生死之境

题意:

幽幽子正在练习符卡[反魂蝶八分咲]
冥界可以抽象成一个无限大的二维平面,其中白玉楼在 ( 0 , 0 ) (0,0) (0,0),冥界上存在着 n n n个幽灵,第 i i i个幽灵位于点 ( x i , y i ) (x_i,y_i) (xi,yi)
一次符卡的释放可以看做一条直线,由于幽幽子身处白玉楼,所以这条直线经过原点。幽幽子想让自己的符卡看起来尽量优美,她定义一次符卡的释放是优美的,当且仅当所有的幽灵在这条直线上的投影是中心对称的。
现在幽幽子想要知道有多少种优美的释放符卡的方式,如果每一种方法都是优美的输出 − 1 -1 1

数据范围:

对于 30 30 30%的数据, n ≤ 10 n\leq10 n10
对于 60 60 60%的数据, n ≤ 200 n\leq200 n200
对于 100 100 100%的数据, n ≤ 1000 , ∣ x i ∣ , ∣ y i ∣ ≤ 1 0 6 , T ≤ 3 n\leq1000,|x_i|,|y_i|\leq10^6,T\leq3 n1000xi,yi106,T3

Analysis:

大力猜结论,暴力出奇迹。
最后投影出来的中心对称点,一定是原点集的重心在这条直线上的对应点。
证明一下:
我们考虑把投影表示成向量的点积形式。若当前有一向量 ( x 0 , y 0 ) (x_0,y_0) (x0,y0)
我们把原点集投影其上, ( x , y ) (x,y) (x,y)会变为 ( x ∗ x 0 , y ∗ y 0 ) (x*x_0,y*y_0) (xx0,yy0)
那么在这条向量上的对称点为: ( ∑ x ∗ x 0 n , ∑ y ∗ y 0 n ) (\frac{\sum{x*x_0}}{n},\frac{\sum{y*y_0}}{n}) (nxx0,nyy0)。将 x 0 x_0 x0提到求和之外。
而原点集重心为: ( ∑ x n , ∑ y n ) (\frac{\sum{x}}{n},\frac{\sum{y}}{n}) (nx,ny)。刚好是它投影出来的向量,证毕。
那我们考虑让每一对对称,我们枚举一对点,它们所连线段的中点与重心连线的法线,移到原点处,即为让他们对称的直线,这比较显然。
然后发现,一条直线合法,至少要出现 n 2 \frac{n}{2} 2n次,那么这样的直线只有最多只有 n n n条。因为一条直线至少会有 n 2 \frac{n}{2} 2n对对称点。
我们排序后暴力判断即可。复杂度 O ( n 2 log ⁡ ( n 2 ) ) O(n^2\log{(n^2)}) O(n2log(n2))

Code:

# include<cstdio>
# include<cstring>
# include<algorithm>
# include<cmath>
using namespace std;
const int N = 1e3 + 5;
const int X = 1e6;
typedef double db;
const db eps = 1e-6;
const db inf = 1e7;
struct node
{
	db x,y;
	node operator + (node r) const
	{ return (node){x + r.x,y + r.y}; }
	node operator * (node r) const
	{ return (node){x * r.x,y * r.y}; }
	bool operator == (node r) const
	{ return fabs(x - r.x) <= eps && fabs(y - r.y) <= eps; }
	bool operator < (node r) const
	{ return fabs(x - r.x) <= eps ? y < r.y : x < r.x; }
}p[N],s[N],z,D;
struct data
{
	db k,b;
	bool operator == (data r) const
	{ return fabs(k - r.k) <= eps; }
	bool operator < (data r) const
	{ return k < r.k; }
}L[N * N];
int vis[N];
int n,T,h,tot,ans;
inline node calc(data x,data y)
{
	db X = y.b / (x.k - y.k),Y = X * x.k;
	return (node){X,Y};
}
inline void solve(int x)
{
	data P;
	if (fabs(L[x].k) > eps) P = (data){-1.0 / L[x].k,0};
	P.b = z.y - z.x * P.k; node mid;
	if (fabs(L[x].k) > eps && fabs(L[x].k - inf) > eps) mid = calc(L[x],P);
	else if (fabs(L[x].k) <= eps) mid = (node){z.x,0};
	else mid = (node){0,z.y};
	for (int i = 1 ; i <= h ; ++i)
	{
		P.b = p[i].y - p[i].x * P.k;
		if (fabs(L[x].k) > eps && fabs(L[x].k - inf) > eps) s[i] = calc(L[x],P);
		else if (fabs(L[x].k) <= eps) s[i] = (node){p[i].x,0};
		else s[i] = (node){0,p[i].y};
	} sort(s + 1,s + h + 1);
	if ((h & 1) && !(s[h / 2 + 1] == mid)) return;
	for (int i = 1 ; i <= h / 2 ; ++i)
	{
		node now = s[i] + s[h - i + 1];
		now.x /= 2.0,now.y /= 2.0;
		if (!(now == mid)) return;
	} ++ans;
}
int main()
{
	freopen("life.in","r",stdin);
	freopen("life.out","w",stdout);
	scanf("%d",&T);
	while (T--)
	{
		scanf("%d",&n); z = (node){0,0},memset(vis,0,sizeof(vis)),ans = 0;
		for (int i = 1 ; i <= n ; ++i) scanf("%lf%lf",&p[i].x,&p[i].y),z = z + p[i];
		z.x /= (db)n,z.y /= (db)n,h = 0;
		z.x *= 2.0,z.y *= 2.0;
		for (int i = 1 ; i <= n ; ++i)
			for (int j = i + 1 ; j <= n ; ++j)
			if ((p[i] + p[j]) == z) vis[i] = vis[j] = 2;
		z.x /= 2.0,z.y /= 2.0,tot = 0;
		for (int i = 1 ; i <= n ; ++i) if (!vis[i]) p[++h] = p[i];
		if (h <= 1) { puts("-1"); continue; }
		for (int i = 1 ; i <= h ; ++i)
			for (int j = i + 1 ; j <= h ; ++j)
			{
				node now = p[i] + p[j];
				now.x /= 2.0,now.y /= 2.0;
				if (fabs(now.x - z.x) <= eps) L[++tot] = (data){0,0};
				else if (fabs(now.y - z.y) <= eps) L[++tot] = (data){inf,0};
				else L[++tot] = (data){-1.0 / ((now.y - z.y) / (now.x - z.x)),0};
			}
		sort(L + 1,L + tot + 1); int las = 1;
		L[tot + 1] = (data){-inf,0};
		for (int i = 1 ; i <= tot ; ++i)
		if (!(L[i] == L[i + 1]))
		{
			if (i - las + 1 >= h / 2) solve(i);
			las = i + 1;
		} printf("%d\n",ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值