题解:P17190 [ICPC 2017 Hong Kong R] Triangle
lailai0916 · · 题解
题意简述
平面上各有
解题思路
一个合法三角形必然由两个同色点与一个异色点组成。先固定两个白点
若
只有恰好一个距离不超过
记
设
对所有白点对求和时,前半部分只依赖每个白点的近邻数。交集部分可以交换枚举顺序。若黑点
两黑一白的三角形完全对称。于是,求出每个点的异色近邻数后,答案就只剩一维计数。
近邻数属于
- 两个值都等于
v 的贡献为v\binom{m_v}{2} ; - 一个值等于
v 、另一个更大时,贡献为v 。
维护此前更小值的数量 pre,便能在线性时间同时计算所有点对的最小值和最大值。最后减去白点与黑点两侧的交集总和即可。
下面求异色近邻数。对每个点作坐标变换:
两点的曼哈顿距离满足:
所以距离不超过
菱形查询由此变成轴平行矩形查询。
把作为数据点的一种颜色按
分别让白点查询黑点、黑点查询白点,就得到两侧全部近邻数。每次建树和查询都是
正确性证明
固定一对同色点时,异色点只有在与二者的距离判定不同时,才产生方向相反的两条异色边。两种同色边定向分别对应集合差
每条同色边只影响包含它的三角形,与其他同色边的选择无关。因此,可以对每个同色点对独立选择较小或较大贡献并求和。一个异色点被交集项计算的次数,正是从它的同色近邻中任选两个的方案数,所以交换求和后的组合数公式正确。
频率统计完整枚举了三类点对:两个值相同、前者较小、前者较大。故 calc 分别得到所有近邻数点对的最小值之和与最大值之和。
坐标变换把曼哈顿球准确变成
程序把白点对和黑点对的贡献相加,并减去各自的交集总和,所以最终最小值与最大值正确。
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=100005;
const int M=2000005;
struct point
{
ll x,y;
};
int n,tot;
ll d;
point a[N],b[N];
ll vx[N],vy[N];
int o[N],rt[N],ls[M],rs[M],sum[M];
int f[N],g[N],cnt[N];
int add(int p,int l,int r,int x)
{
int u=++tot;
ls[u]=ls[p];
rs[u]=rs[p];
sum[u]=sum[p]+1;
if(l<r)
{
int mid=(l+r)>>1;
if(x<=mid)ls[u]=add(ls[p],l,mid,x);
else rs[u]=add(rs[p],mid+1,r,x);
}
return u;
}
int ask(int p,int l,int r,int x,int y)
{
if(x<=l&&r<=y)return sum[p];
int mid=(l+r)>>1,res=0;
if(x<=mid)res+=ask(ls[p],l,mid,x,y);
if(y>mid)res+=ask(rs[p],mid+1,r,x,y);
return res;
}
void work(point q[],point p[],int c[])
{
for(int i=1;i<=n;i++)
{
o[i]=i;
vy[i]=p[i].y;
c[i]=0;
}
sort(o+1,o+n+1,[&](int x,int y){return p[x].x<p[y].x;});
for(int i=1;i<=n;i++)vx[i]=p[o[i]].x;
sort(vy+1,vy+n+1);
int m=unique(vy+1,vy+n+1)-vy-1;
tot=0;
rt[0]=0;
for(int i=1;i<=n;i++)
{
int x=lower_bound(vy+1,vy+m+1,p[o[i]].y)-vy;
rt[i]=add(rt[i-1],1,m,x);
}
for(int i=1;i<=n;i++)
{
int l=lower_bound(vx+1,vx+n+1,q[i].x-d)-vx;
int r=upper_bound(vx+1,vx+n+1,q[i].x+d)-vx-1;
int x=lower_bound(vy+1,vy+m+1,q[i].y-d)-vy;
int y=upper_bound(vy+1,vy+m+1,q[i].y+d)-vy-1;
if(l<=r&&x<=y)c[i]=ask(rt[r],1,m,x,y)-ask(rt[l-1],1,m,x,y);
}
}
pair<ll,ll> calc(int a[])
{
fill(cnt,cnt+n+1,0);
for(int i=1;i<=n;i++)cnt[a[i]]++;
ll mn=0,mx=0,pre=0;
for(int i=0;i<=n;i++)
{
ll c=cnt[i];
ll eq=c*(c-1)/2*i;
mn+=1LL*i*c*(n-pre-c)+eq;
mx+=1LL*i*c*pre+eq;
pre+=c;
}
return {mn,mx};
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
while(cin>>n>>d)
{
for(int i=1;i<=n;i++)
{
ll x,y;
cin>>x>>y;
a[i]={x+y,x-y};
}
for(int i=1;i<=n;i++)
{
ll x,y;
cin>>x>>y;
b[i]={x+y,x-y};
}
work(a,b,f);
work(b,a,g);
auto x=calc(f),y=calc(g);
ll s=0;
for(int i=1;i<=n;i++)
{
s+=1LL*f[i]*(f[i]-1)/2;
s+=1LL*g[i]*(g[i]-1)/2;
}
cout<<x.first+y.first-s<<' '<<x.second+y.second-s<<'\n';
}
return 0;
}