题解:P16537 [THUPC 2026 决赛] 星图重绘
lailai0916 · · 题解
题意简述
给定横、纵坐标均两两不同的
解题思路
将一个三角形的三个顶点按横坐标排序,记为
这个条件不仅充分,而且必要:任何包含三个点的轴对齐矩形都包含它们的最小外接矩形。若
由此得到一个计数上界。若
因此,每个三角形必须使用一条独占的竖直射线,并且:
- 向下的射线可用,当且仅当该点左右两侧各有一个纵坐标更小的点。
- 向上的射线可用,当且仅当该点左右两侧各有一个纵坐标更大的点。
所求数量不超过这两类可用射线的总数。下面构造一组方案,恰好把它们全部用上。
先按纵坐标从小到大插入所有点,用有序集合维护已经插入的点,按横坐标排序。插入点
因为
这一遍产生的三角形内部两两不交。考虑把当前集合中的点按横坐标依次连接,形成一条折线。插入
已有三角形都位于原折线下方,新三角形位于原折线上方,因此不会与已有三角形的内部相交。更新后,所有三角形仍位于新折线下方。若新点插入在横坐标最左端或最右端,仅扩展折线,不产生三角形,已有区域也不会受到影响。这个性质从空集合开始始终成立。
然后按纵坐标从大到小,再独立执行一次相同操作。此时新点低于左右邻居,新三角形位于旧折线下方;这一遍的所有三角形最终都位于折线上方,并且内部两两不交。它们恰好使用全部可用的向上射线。
两遍结束时的折线相同,都是将全部点按横坐标顺序连接。第一遍的三角形在最终折线下方,第二遍的三角形在最终折线上方。因此两遍之间也不会发生内部相交,折线上的边界重合则是允许的。
每条可用竖直射线恰好贡献一个三角形,构造数量达到上界,所以方案最优。
实现中保留原始编号 id,每次插入后用有序集合的前驱和后继取得左右邻居。solve 完成一遍构造,反转按纵坐标排序的数组后再次调用。每遍至多产生
排序和两遍集合操作的总时间复杂度为
参考代码
#include <bits/stdc++.h>
using namespace std;
const int N=200005;
const int M=400005;
struct node
{
int x,y,id;
}a[N];
array<int,3> ans[M];
int cnt;
bool cmp(node a,node b){return a.y<b.y;}
void solve(int n)
{
set<pair<int,int>> s;
for(int i=1;i<=n;i++)
{
auto it=s.insert({a[i].x,a[i].id}).first;
if(it==s.begin()||next(it)==s.end())continue;
ans[cnt++]={prev(it)->second,a[i].id,next(it)->second};
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--)
{
int n;
cin>>n;
for(int i=1;i<=n;i++){cin>>a[i].x>>a[i].y;a[i].id=i;}
sort(a+1,a+n+1,cmp);
cnt=0;
solve(n);
reverse(a+1,a+n+1);
solve(n);
cout<<cnt<<'\n';
for(int i=0;i<cnt;i++)cout<<ans[i][0]<<' '<<ans[i][1]<<' '<<ans[i][2]<<'\n';
}
return 0;
}