题解:AT_abc131_f [ABC131F] Must Be Rectangular!

题意

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;
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值