题解:P16092 [ICPC 2024 NAC] Shadow Line
lailai0916 · · 题解
题意简述
点光源从原点沿负半轴移动,若干竖直线段在右侧墙上投下阴影。求阴影并集恰好为一个区间时,光源位置集合的总长度;若包含无界区间,输出
解题思路
设光源位于
所有端点都乘上同一个正因子
每个线段的下端点记为
在没有端点重合的时刻,覆盖次数每回到
先判断答案是否无界。当
否则,最后一个端点换序事件之后不会再出现合法位置,仅需处理有限个事件之间的区间。
固定将端点按
分母恒正,仅保留分子为正的事件。任意一对端点至多交换一次顺序,所以全部事件不超过
事件时刻不能用浮点数排序或分组。保存分子和分母,通过交叉相乘比较;两个分数交叉乘积相等时,必须作为同一批处理。坐标限制下,分子绝对值不超过 long long 精确计算。
初始化时按
同时发生的事件不能逐条随意交换。若多个端点在同一位置相交,处理其中一对后,另一对未必仍然相邻;同一时刻也可能有多组端点分别在不同高度相交。
对同一个时刻的全部相交端点建立临时并查集,将每条事件连接的两个端点合并。一个连通分量中的端点在这个时刻具有相同高度;不同分量则对应不同的相交位置。
每个分量在事件前的端点序列中必然连续。若两个即将相交的端点之间夹着另一个端点,令时间趋近事件时刻,根据高度的连续性,中间端点也必须趋近同一个高度,因此也属于这一分量。所有这类端点对的相交事件都会被枚举到,不会漏掉中间端点。
每个分量内部的顺序在事件后完全反转。对于任意两个端点,等高条件的分子是关于
并查集维护每个分量在当前排列中的最小、最大位置。合并完整批次后,对每个分量对应的连续区间执行反转。设该区间为
不同分量的区间互不相交,可以依次处理,即使它们紧邻也不受影响。代码中的 a 保存端点排列,pos 保存每个端点在排列中的位置,sum 保存覆盖前缀,cnt 保存连通块数。并查集的 mn 和 mx 在整批事件完成合并前保持为旧排列中的区间边界。
若上一事件时刻为 cnt==1,将这段长度加入答案。为避免接近的两个大浮点数直接相减,先精确计算差的分子,再转换为浮点数:
处理有限个事件点本身不会改变总长度:即使阴影仅在某个瞬间连通,这个单点的长度也为
大小为 n 加倍,后续它表示端点总数。
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
using ld=long double;
const int N=6005;
const int M=17997005;
struct P
{
int x,y,op;
bool operator<(const P &v)const{return y<v.y;}
}p[N];
struct E
{
ll a;
int b,u;
bool operator<(const E &v)const{return a*v.b<v.a*b;}
}e[M];
int a[N],pos[N],sum[N],fa[N],mn[N],mx[N],vis[N],q[N];
int find(int u){return u==fa[u]?u:fa[u]=find(fa[u]);}
void merge(int u,int v)
{
u=find(u);v=find(v);
if(u==v)return;
fa[u]=v;
mn[v]=min(mn[v],mn[u]);
mx[v]=max(mx[v],mx[u]);
}
void init(int u,int tag,int &cnt)
{
if(vis[u]==tag)return;
vis[u]=tag;
fa[u]=u;
mn[u]=mx[u]=pos[u];
q[cnt++]=u;
}
bool cmp(int u,int v)
{
ll x=1LL*p[u].y*p[v].x,y=1LL*p[v].y*p[u].x;
return x!=y?x<y:u<v;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,w;
cin>>n>>w;
for(int i=0;i<n;i++)
{
int x,l,r;
cin>>x>>l>>r;
p[i*2]={x,l,1};
p[i*2+1]={x,r,-1};
}
n*=2;
sort(p,p+n);
int cur=0,cnt=0;
for(int i=0;i<n;i++)
{
cur+=p[i].op;
cnt+=cur==0;
}
if(cnt==1){cout<<-1<<'\n';return 0;}
for(int i=1;i<=n;i++)a[i]=i-1;
sort(a+1,a+n+1,cmp);
cnt=0;
for(int i=1;i<=n;i++)
{
pos[a[i]]=i;
sum[i]=sum[i-1]+p[a[i]].op;
cnt+=sum[i]==0;
}
int tot=0;
for(int i=0;i<n;i++)
{
for(int j=i+1;j<n;j++)
{
ll num=1LL*p[i].y*p[j].x-1LL*p[j].y*p[i].x;
if(num>0)e[tot++]={num,p[j].y-p[i].y,i*n+j};
}
}
sort(e,e+tot);
ld ans=0;
ll pre=0;
int den=1;
for(int i=0;i<tot;)
{
int j=i+1;
while(j<tot&&e[j].a*e[i].b==e[i].a*e[j].b)j++;
if(cnt==1)ans+=(ld)(e[i].a*den-pre*e[i].b)/den/e[i].b;
pre=e[i].a;
den=e[i].b;
int num=0;
for(int k=i;k<j;k++)
{
int u=e[k].u/n,v=e[k].u%n;
init(u,i+1,num);init(v,i+1,num);
merge(u,v);
}
for(int k=0;k<num;k++)
{
int u=q[k];
if(fa[u]!=u)continue;
int l=mn[u],r=mx[u];
reverse(a+l,a+r+1);
for(int i=l;i<=r;i++)
{
cnt-=sum[i]==0;
sum[i]=sum[i-1]+p[a[i]].op;
cnt+=sum[i]==0;
pos[a[i]]=i;
}
}
i=j;
}
cout<<fixed<<setprecision(12)<<ans<<'\n';
return 0;
}