「JOISC 2014 Day3」稻草人

题意

在一个二维平面上,给定点集 $S$,求所有满足

  • $\large{x_i\lt x_j}$;
  • $\large{y_i\lt y_j}$;
  • $\large{\forall k\in S\cap\{i,j\}}$,有 $\large{x_k\notin[x_i,x_j],y_k\notin [y_i,y_j]}$;

的点对 $(i,j)$ 的数量。

Solution

发现对于一个点 $i$ 而言,对答案的贡献应为在它右上方的点所形成的凸包的大小。即其右上方 $x$ 最接近于 $x_i$ 和 $y$ 最接近于 $y_i$ 的两个点之间形成的类下凸包大小。令二者分别为 $pre_i$ 和 $r_i$。

形式化地,$\forall k\in T_i$,有 $x_i\lt x_k,y_i\lt y_k$。

则 $pre_i \in T_i$,$\forall k \in T_i$,有 $\lvert x_{pre_{j}}-x_i\lvert \leq \lvert x_k-x_i\lvert $。

同样地,有 $r_i \in T_i$,$\forall k \in T_i$,有 $\lvert y_{r_{j}}-y_i\lvert \leq \lvert y_k-y_i\lvert $。

则对答案可以产生贡献的点集为 $pre_i,a_1,\cdots,a_p,r_i$,满足 $a_i\in T_i$,$x_{a_{i-1}}\lt x_{a_i}\lt x_{a_{i+1}}$,且 $y_{a_{i-1}}\lt y_{a_i}\lt y_{a_{i+1}}$。

对于每个数,单调栈维护类下凸包即可,时间复杂度为 $\mathcal{O}(n^2)$。

考虑优化,发现不必要对于每个点重新计算答案,考虑对于 $x$ 或 $y$ 区间分治并合并答案。以分治 $x$ 坐标为例,从 $y_{max}$ 向下不断枚举。对于 $[mid+1,right]$ 区间,维护一个类下凸包,每个点满足在前一个点的右下方。对于 $[left,mid]$ 区间,也维护一个类下凸包,每个点满足在前一个点的左下方,对于新加入的点 $i$,在类下凸包中的前一个点对 $i$ 对答案贡献的影响最大,即前文提到的 $pre_i$。在右区间的类下凸包中,所有 $y\ge y_{pre_{i}}$ 的点都不产生贡献了,可以在右区间的单调栈中二分找到分界。

时间复杂度为 $\mathcal{O}(n\log^2n)$,可以通过本题。

code

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
    int res=0,flag=1;
    char ch=getchar();
    while(!isalnum(ch)) (ch=='-')?flag=-1:1,ch=getchar();
    while(isalnum(ch)) res=res*10+ch-'0',ch=getchar();
    return res*flag;
}
struct Stack
{
    int head;
    std::pair<int,int> val[200010];
    void push(pair<int,int> value)
    {
        val[++head]=value;
        return ;
    }
    void pop() { head--; return ; }
    pair<int,int> top() { return val[head]; }
    bool empty() { return !head; }
    void clear() { head=0; return ; }
    int solve(int pos)
    {
        int left=1,right=head,res=head+1;
        while(left<=right)
        {
            int mid=(left+right)>>1;
            if(val[mid].second<pos)
                res=mid,right=mid-1;
            else
                left=mid+1;
        }
        if(res==head+1)
            return 0;
        return head-res+1;
    }
};
struct Stack a,b;
int n;
long long ans;
void solve(int left,int right,vector<pair<int,int> > val)
{
    b.push(make_pair(0x3f3f3f3f,0x3f3f3f3f));
    if(left==right)
    {
        ans+=val.size()-1;
        return ;
    }
    int mid=(left+right)>>1;
    vector<pair<int,int> > l,r;
    l.clear(),r.clear();
    for(auto i=val.begin();i!=val.end();i++)
    {
        if(i->first<=mid)
        {
            l.push_back(*i);
            while(b.empty()==false&&b.top().first<i->first)
                b.pop();
            ans+=a.solve(b.top().second);
            b.push(*i);
        }
        if(i->first>mid)
        {
            r.push_back(*i);
            while(a.empty()==false&&a.top().first>=i->first)
                a.pop();
            a.push(*i);
        }
    }
    a.clear(),b.clear();
    if(l.size()>=1) solve(left,mid,l);
    if(r.size()>=1) solve(mid+1,right,r);
    return ;
}
int main(int argc,const char *argv[])
{
    n=read();
    int left=INT_MAX,right=0;
    vector<pair<int,int> > val;
    for(int i=1;i<=n;i++)
    {
        int x=read(),y=read();
        val.push_back(make_pair(x,y));
        left=std::min(left,x);
        right=std::max(right,x);
    }
    std::sort(val.begin(),val.end(),[](pair<int,int> a,pair<int,int> b){return (a.second==b.second)?(a.first>b.first):(a.second>b.second);});
    solve(left,right,val);
    printf("%lld\n",ans);
    return 0;
}
posted @ 2023-08-03 00:24  Che_001  阅读(85)  评论(0)    收藏  举报  来源