题意
有 n n n 个点,假如有 ( a , b ) (a,b) (a,b), ( a , d ) (a,d) (a,d), ( c , b ) (c,b) (c,b), ( c , d ) (c,d) (c,d) 中任意 3 3 3 个点,则可以添加剩下那一个点,求最多可以多放多少个点。
分析
首先,我们我们观察如下两张图。
图一中黑圆代表原本有的点,图二的红勾代表新增的点。我们不难发现,其实题目就是想让我们找出若干联通块,让每个联通块拼成一个长方形。
那么如何求联通块呢?我们可以用到并查集。我们可以将横坐标相同的点的编号合并,再将纵坐标相同的点的编号合并,我们就可以得到联通块了。对于每一个联通块,我们只需要用 set
求出横坐标和纵坐标分别有多少个不重复的坐标,也就是联通块的长度和宽度。最后我们用长度和宽度相乘,再减去联通块原有的点就是答案了。
于是,我们不难调出如下代码。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,ans;
struct Point{
int x,y;
}a[100005];//记录点的信息
set<int> xx[100005],yy[100005];//存每个点的横,纵坐标的可能
int father[100005],cnt[100005];//每个点的父节点和数量
bool cmp1(Point x,Point y){return x.x<y.x;}//按横坐标排
bool cmp2(Point x,Point y){return x.y<y.y;}//按纵坐标排
int find(int x){return x==father[x]?x:father[x]=find(father[x]);}//找祖先
void add(int X,int Y){
int x=find(X),y=find(Y);
if(x==y) return;//一样就不用合并
father[y]=x,cnt[x]+=cnt[y];//合并
for(auto i:xx[y]) xx[x].insert(i);//将两个点的横坐标的所有可能更新
for(auto i:yy[y]) yy[x].insert(i);//将两个点的纵坐标的所有可能更新
}
map<pair<int,int> ,int> num;//存原本编号
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++){
scanf("%lld%lld",&a[i].x,&a[i].y);
num[make_pair(a[i].x,a[i].y)]=father[i]=i,cnt[i]=1;//初始化
xx[i].insert(a[i].x),yy[i].insert(a[i].y);//初始化
}
sort(a+1,a+n+1,cmp1);
for(int i=2;i<=n;i++) if(a[i].x==a[i-1].x) add(num[make_pair(a[i].x,a[i].y)],num[make_pair(a[i-1].x,a[i-1].y)]);//合并
sort(a+1,a+n+1,cmp2);
for(int i=2;i<=n;i++) if(a[i].y==a[i-1].y) add(num[make_pair(a[i].x,a[i].y)],num[make_pair(a[i-1].x,a[i-1].y)]);//合并
for(int i=1;i<=n;i++) if(find(i)==i) ans+=(xx[i].size()*yy[i].size())-cnt[i];//求答案,并累加
printf("%lld\n",ans);
return 0;
}
但是,你会发现,超时!
所以,我们必须将它优化。首先,我们可以把 map
删了,在结构体里多加一个变量记录原本编号,并再用数组记录原本对应的横坐标与纵坐标。其次,我们可以再合并的时候比较一下怎样合并次数更少,就用那种方法合并。
可是,依然超时。我们很容易看出,导致超时的其实就是合并后将两个点的坐标的可能更新太慢。所以我们可以记录一下每个点分别有哪些点与其合并。最后在求的时候,用搜索搜出即可。于是,我们终于调出正确代码。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,ans;
struct Point{
int x,y,num;//num用来记录原本对应的值
}a[100005],Jl[100005];//Jl用来记录原本点的坐标
set<int> xx[100005],yy[100005];
int father[100005],cnt[100005];
bool cmp1(Point x,Point y){return x.x<y.x;}
bool cmp2(Point x,Point y){return x.y<y.y;}
int find(int x){return x==father[x]?x:father[x]=find(father[x]);}
vector<int> jl[100005];//记录每个点与其合并的点
void add(int X,int Y){
int x=find(X),y=find(Y);
if(x==y) return;
if((xx[y].size()+yy[y].size())<(xx[x].size()+yy[x].size())) swap(x,y);
father[y]=x;
cnt[x]+=cnt[y];
jl[x].push_back(y);//加入
}
queue<int> q;//用于后面bfs
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++){
scanf("%lld%lld",&a[i].x,&a[i].y);
Jl[i].x=a[i].x,Jl[i].y=a[i].y;
a[i].num=father[i]=i,cnt[i]=1;
}
sort(a+1,a+n+1,cmp1);
for(int i=2;i<=n;i++) if(a[i].x==a[i-1].x) add(a[i].num,a[i-1].num);
sort(a+1,a+n+1,cmp2);
for(int i=2;i<=n;i++) if(a[i].y==a[i-1].y) add(a[i].num,a[i-1].num);
for(int i=1;i<=n;i++)
if(father[i]==i){
q.push(i);
while(!q.empty()){
int now=q.front();
xx[i].insert(Jl[now].x),yy[i].insert(Jl[now].y);//放入set中
q.pop();
for(auto i:jl[now]) q.push(i);
}//bfs
ans+=(xx[i].size()*yy[i].size())-cnt[i];//更新
}
printf("%lld\n",ans);
return 0;
}