知识点:数据结构、分组、双指针、二分、STL、思维

题目链接

题意

1 0 6 × 1 0 6 10^{6} \times 10^{6} 106×106的整点网格的网格线上分布n条竖直线和m条横直线(不重叠),直线上分布k个整点,求两点沿已知直线行走的最短路径长度不等于两点的Manhattan距离组成的对数(无序)。

思路

参考awoo的CF官方题解
在两直线之间记为行或列,两点不在同行同列的情况下两种情况均不满足题意:
1、两点都在各自的横/竖线,必能经过两点之间夹的一条竖/横线达到Manhattan距离。
2、一点在横线,一点在竖线,经过两点所在直线的交点的路径达到Manhattan距离。
此外,同一线上的点不满足题意;即同行同列的不在同一线上的两点满足题意。
对同行同列的点分组,每组点再按在同一直线上的点分组,按行排序组,枚举组,答案每次加上该组点数量乘以之前点数量。
实现思路见代码。

代码

#include"bits/stdc++.h"
#define ll long long
#define db(x) cout<<fixed<<setprecision(14)<<#x<<':'<<(x)<<endl
#define pb push_back
#define x first
#define y second
#define max(a,b) (a<b?b:a)
#define mix(a,b) (a<b?a:b)
#define vec vector<ll>
#define mid (l+r>>1)
#define sl rt<<1,l,mid
#define sr rt<<1|1,mid+1,r
#define tdata 1,1,n
#define tinfo ll rt,ll l,ll r
#define all(a) (a).begin(),(a).end()
using namespace std;
const ll N=2e5+10,M=3e5+10,inf=1e18,mod=1e9+7;

ll t;
vec roadx,roady;//竖线横坐标,横线纵坐标
vector<pair<ll,ll> > pnt[2];//pnt[0]在同一**竖线**上的点,pnt[1]…横线…

void init(ll n,ll m) {
   
  roadx.resize(n);
  roady.resize(m);
  pnt[0].resize(0);
  pnt[1].resize(0);
}

int main() {
   
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  cin>>t;
  while(t--) {
   
    ll n,m,k;
    cin>>n>>m>>k;
    init(n,m);
    for(ll i=0; i<n; ++i) cin>>roadx[i];
    for(ll i=0; i<m; ++i) cin>>roady[i];
    for(ll i=0; i<k; ++i) {
   
      ll x,y;
      cin>>x>>y;
      //在横线或竖线上的点分组,类离散化给定点横纵坐标的组号,组号按照行/列递增,后面代码注释组号记为x,y
      ll lx=upper_bound(all(roadx),x)-roadx.begin()-1;//ex. 2.7->2
      ll ly=upper_bound(all(roady),y)-roady.begin()-1;
      if(x==roadx[lx]&&y==roady[ly]) continue;//忽略十字线上的点
      if(x==roadx[lx])
        pnt[0].pb({
   roadx[lx],roady[ly]});
      else pnt[1].pb({
   roady[ly],roadx[lx]});//与后面代码的交换算法对应
    }
    ll ans=0;
    //处理竖线,处理横线
    for(ll i=0; i<2; ++i) {
   
      auto &group=pnt[i];//取别名
      sort(all(group),[](pair<ll,ll> a,pair<ll,ll> b) {
   
        return a.y==b.y?a.x<b.x:a.y<b.y;
      });//y第一关键字,x第二关键字排序(枚举顺序)
      //同行同***定区间分组
      ll l=0,r=0;
      while(r<group.size()) {
   
        r=upper_bound(group.begin()+l,group.end(),group[l],[](pair<ll,ll> a,pair<ll,ll> b) {
   
          return a.y<b.y;
        })-group.begin();//二分y,找到区间内y相等的右端点
        //同一直线上的点确定区间分组
        ll p=l,q=l;
        //之前点的数量
        ll cnt=0;
        while(q<r) {
   
          q=upper_bound(group.begin()+p,group.begin()+r,group[p],[](pair<ll,ll> a,pair<ll,ll> b) {
   
            return a.x<b.x;
          })-group.begin();//二分x,找到区间内x相等的右端点
          ans+=cnt*(q-p);//之前点数量乘以该组点数量
          cnt+=q-p;//更新
          p=q;//移动双指针
        }
        l=r;//移动双指针
      }
      //在行上的点转化为在列上的点,相当于翻转坐标系,重复以上算法
      if(i==0) {
   
        swap(n,m);
        swap(roadx,roady);
      }
    }
    cout<<ans<<endl;
  }
  return 0;
}