[转载]CF1028FMakeSymmetrical题解--性质+STL骚操作

闲扯

集训时侯讲到这题,然而出题人简洁的题解跟没有差不多,然后上网看applese大佬的博客终于看懂了,不违反基本法还是贴[转载]吧

题目链接

https://www.luogu.org/problemnew/show/CF1028F

转载来源

https://blog.csdn.net/effervescence/article/details/82142380


题意

 给一个无限大的二维平面,$n(n≤2 \times 10^5)$次操作,

 (1)在平面上加一个点(保证加入前不存在)

 (2)在平面上删除一个点(保证删除时存在)

 (3)给出一个点,以原点和这个点连成的直线为对称轴,问你至少要加几个点可以使得这个平面对称。

分析

首先我们猜一个结论,就是一个以原点为圆心的圆上的整点是不会很多的(具体我不会证)。

然后又因为如果这个平面对称,那么两个对称点到原点的距离肯定是相等的。那么我们每次加入或删除一个点时,记这个点到原点的距离为d,那我们暴力枚举这个以d为半径,原点为圆心的圆上其他的点,算出他们的对称轴,把这个对称轴的数值加上或减去2,然后把这个点和原点连成的直线所形成的对称轴的数值加上或减去1,之后每次O(1)回答答案就行了

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
bool Finish_read;
template<class T>inline void read(T &x){Finish_read=0;x=0;int f=1;char ch=getchar();while(!isdigit(ch)){if(ch=='-')f=-1;if(ch==EOF)return;ch=getchar();}while(isdigit(ch))x=x*10+ch-'0',ch=getchar();x*=f;Finish_read=1;}
template<class T>inline void print(T x){if(x/10!=0)print(x/10);putchar(x%10+'0');}
template<class T>inline void writeln(T x){if(x<0)putchar('-');x=abs(x);print(x);putchar('\n');}
template<class T>inline void write(T x){if(x<0)putchar('-');x=abs(x);print(x);}
/*================Header Template==============*/
struct Point {
int x,y;
Point(int _x=0,int _y=0) {
x=_x,y=_y;
}
inline bool operator < (const Point &rhs) const {
return x==rhs.x?y<rhs.y:x<rhs.x;
}
inline Point F() {
Point p=Point(x,y);
if(x||y) {
int g=__gcd(p.x,p.y);
p.x/=g,p.y/=g;
}
return p;
}
inline Point operator + (const Point &rhs) const {
return Point(x+rhs.x,y+rhs.y).F();
}
};
int n,cnt;
map<ll,set<Point> >circle;
map<Point,int>exist;
Point o(0,0);
int main() {
read(n);
while(n--) {
int ty,x,y;
read(ty),read(x),read(y);
Point t=Point(x,y);
ll d=1ll*x*x+1ll*y*y;
if(ty==1) {
for(auto p:circle[d])
exist[p+t]+=2;
circle[d].insert(t);
exist[o+t]++,cnt++;
}
if(ty==2) {
circle[d].erase(circle[d].find(t));
for(auto p:circle[d])
exist[p+t]-=2;
exist[o+t]--,cnt--;
}
if(ty==3)
printf("%d\n",cnt-exist[o+t]);
}
}

一些补充

如果您已经看懂applese大佬的博客那么您可以不用看下面了,这些是我对我在阅读时一些疑惑的强行解释

由于对称轴已经有一点是固定的,那么它经过的另一点(除原点)不同肯定直线也就不同,那么对于两点$A(x_a,y_a),B(x_b,y_b)$,很显然它们的对称轴经过中点$C((x_a+x_b)/2,(y_a+y_b)/2)$,于是它们的对称轴其实就可以用$C$点表示,但是为了方便起见不妨用$C`(x_a+x_b,y_a+y_b)$表示对称轴,但注意要保证$(x_a+x_b)$与$(y_a+y_b)$是互质的,否则就可能有些在一条对称轴上的点算不到

所以$exist[PT]$其实就是表示关于过$(0,0)$与$PT$两点为对称轴的点的个数,询问就是总的点数减去对称的点数就好了

不含C++11特性代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <queue>
#include <iostream>
#include <set>
#include <map>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>
#define ll long long
#define ri register int
using namespace std;
using namespace __gnu_pbds;
int n,tot;
template <class T>inline void read(T &x){x=0;int ne=0;char c;
while(!isdigit(c=getchar()))ne=c=='-';x=c-48;
while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-48;x=ne?-x:x;
}
int gcd(int a,int b){return b?gcd(b,a%b):a;}
struct Pt{
int x,y;
Pt(int _x,int _y){x=_x,y=_y;}
Pt operator +(const Pt &b)const{
int p=gcd(x+b.x,y+b.y);
return Pt((x+b.x)/p,(y+b.y)/p);
}
bool operator <(const Pt &b)const{
return x==b.x?y<b.y:x<b.x;
}
};
map<ll,set<Pt> > c;
map<Pt,int> g;
int main(){
int op,x,y;
Pt O=Pt(0,0);tot=0;
read(n);
set<Pt>::iterator k;
while(n--){
read(op),read(x),read(y);
Pt pt=Pt(x,y);
ll dis=1ll*x*x+1ll*y*y;
if(op==1){
for(k=c[dis].begin();k!=c[dis].end();k++){g[*k+pt]+=2;}
c[dis].insert(pt);
tot++;g[O+pt]++;
//Pt y=O+pt;
//printf("%d %d\n",y.x,y.y);
}
else if(op==2){
c[dis].erase(c[dis].find(pt));
for(k=c[dis].begin();k!=c[dis].end();k++)g[*k+pt]-=2;
tot--;g[O+pt]--;
}
else{
//Pt y=O+pt;
//printf("%d %d %d\n",y.x,y.y,g[y]);
printf("%d\n",tot-g[O+pt]);
}
}
return 0;
}