CDQ分治——NKOJ3210 围牛群

本文介绍了一种利用CDQ分治算法处理点集维护及直线位置关系查询的问题。通过对输入点集进行凸包构造,并结合三分法确定极值,实现了高效查询所有点是否位于给定直线的同一侧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:
维护一个点集,支持两种操作:
(1) 插入点P(x,y).
(2)询问所有点是否在 直线Ax+By=C的同一侧。
分析:
显然可以用平衡树维护凸包,每次二分查找切线。但是代码比较复杂。
考虑cdq分治,按照输入的顺序,左区间的点对右区间的询问都会有影响。所以直接求出左区间点构成的凸包,静态查询右边区间的直线是否切割凸包即可。
对于直线Ax+By+C=0,令f(x)=Ax+By+C. f(x)的正负对应了点和直线的位置关系。并且|f(x)|正比于点到直线的距离。所以凸包上的点的f(x)值一定是单峰函数,所以可以用三分法求出极值,再判断正负即可。
三分好容易写挂,注意特判一下端点。
由此可见,CDQ分治在这个问题上和 平衡树维护凸包其实是完全等价的。
代码如下:

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#define LL long long

using namespace std;
const int maxn=300000+5;
const LL inf= 999999999999999LL;

template <class T>
inline void _read(T &x){
    char ch=getchar(); bool mark=false;
    for(;!isdigit(ch);ch=getchar())if(ch=='-')mark=true;
    for(x=0;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    if(mark)x=-x;
}

int n,qq,N;
//Definition of Point and Vector
struct Point {
    LL x,y;
    Point(){}
    Point (LL x,LL y):x(x),y(y){};
    bool operator < (const Point p)const {
        return x<p.x||(x==p.x&&y<p.y);
    }
    void put(){
        cout<<"("<<x<<","<<y<<")";
    }
};

typedef Point Vector;
Vector operator + (Vector A,Vector B){return Vector(A.x+B.x,A.y+B.y);}
Vector operator - (Vector A,Vector B){return Vector(A.x-B.x,A.y-B.y);}
LL Dot(Vector A,Vector B){return A.x*B.x+A.y*B.y;}
LL Cross(Vector A,Vector B){return A.x*B.y-A.y*B.x;}

//求凸包 返回 下凸包CH[0~rear1-1],上凸包CH[rear1~raer2-1]; 
Point CH[maxn];
int rear1,rear;
void ConvexHull(Point P[],int n){
    //cout<<"IN function ConvexHull: n= "<<n<<endl;
    //cout<<"Point[]:";for(int i=1;i<=n;i++)P[i].put();cout<<endl;
    sort(P+1,P+1+n);
    int i;
    rear=0;
    for(i=1;i<=n;i++){
        while(rear>1&&Cross(CH[rear-1]-CH[rear-2],P[i]-CH[rear-2])<=0) rear--;
        CH[rear++]= P[i];
    }
    rear1=rear;
    for(i=n-1;i>0;i--){
        while(rear>rear1 && Cross(CH[rear-1]-CH[rear-2],P[i]-CH[rear-2])<=0) rear--;
        CH[rear++]= P[i];
    }
    if(n>1) rear--;
    //cout<<"return with rear= "<<rear<<" and rear1= "<<rear1<<endl; 
}

struct operation{
    LL x,y,z,Max;
    int ans;
}op[maxn]; 

LL F(int l,Point p){
    return op[l].x*p.x+op[l].y*p.y+op[l].z;
}
LL f(LL x){if(!x)return 0 ;return x>0? 1:-1;}
//三分求 Max & Min 
Point Q[maxn];
LL GetMax(int L,int R,int l){
    if(R<L) return -inf;
    LL Max= max(F(l,CH[L]),F(l,CH[R]));
    while(true){
        if(R-L<=5){
            for(int i=L;i<=R;i++) Max=max(Max,F(l,CH[i]));
            return Max;
        }
        int lmid=L+(R-L+1)/3,rmid=R-(R-L+1)/3;
        LL f1=F(l,CH[lmid]),f2=F(l,CH[rmid]);
        if(f1>f2) R=rmid;
        else L=lmid;
    }
} 

LL GetMin(int L,int R,int l){
    if(R<L) return inf;
    LL Min= min(F(l,CH[L]),F(l,CH[R]));
    while(true){
        if(R-L<=5){
            for(int i=L;i<=R;i++) Min=min(Min,F(l,CH[i]));
            return Min;
        }
        int lmid=L+(R-L+1)/3,rmid=R-(R-L+1)/3;
        LL f1=F(l,CH[lmid]),f2=F(l,CH[rmid]);
        if(f1<f2) R=rmid;
        else L=lmid;
    }
} 

//CDQ
void Solve(int L,int R){
    if(L==R) return ;
    int mid=(L+R)>>1;
    Solve(L,mid);Solve(mid+1,R);
    int i,j,m=0;
    for(i=L;i<=mid;i++)
        if(op[i].ans==-1) Q[++m]=Point(op[i].x,op[i].y);
    if(!m) return ;
    ConvexHull(Q,m);
    for(i=mid+1;i<=R;i++)
        if(op[i].ans==0){
            LL Max=max( GetMax(0,rear1-1,i), GetMax(rear1,rear-1,i) );
            LL Min=min( GetMin(0,rear1-1,i), GetMin(rear1,rear-1,i) );
            if(Max==0||Min==0||f(Max)*f(Min)<0|| (f(Max)*f(op[i].Max)<0) )
                op[i].ans=1;
            op[i].Max= f(Max);
        }
}

int main(){
    int i,j,x,y,k;
    _read(n); _read(qq);
    for(i=1;i<=n;i++){
        _read(op[i].x); _read(op[i].y);
        op[i].ans=-1;
    }
    N=qq+n;
    for(i=n+1;i<=N;i++){
        _read(k);
        if(k==1){
            _read(op[i].x); _read(op[i].y);
            op[i].ans=-1;
        }
        else {
            _read(op[i].x); _read(op[i].y); _read(op[i].z);
            op[i].z*=-1;
        }
    }
    Solve(1,N);
    for(i=1;i<=N;i++)
        if(op[i].ans!=-1)
            puts(op[i].ans==0? "YES":"NO");
    return 0;
}
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<cmath>
#define ll long long
using namespace std;
const ll inf=1000000000000000LL;
template <typename T>
inline void _read(T& x){
    char t=getchar();bool sign=true;
    while(t<'0'||t>'9')
    {if(t=='-')sign=false;t=getchar();}
    for(x=0;t>='0'&&t<='9';t=getchar())x=x*10+t-'0';
    if(!sign)x=-x;
}
int n,q;
struct point{
    ll x,y;
    point(){}
    point(ll g,ll h){x=g;y=h;}
    bool operator < (const point G) const {
        if(x==G.x)return y<G.y;
        else return x<G.x;
    }
    point operator + (const point G) const {
        return point(x+G.x,y+G.y);
    }
    point operator - (const point G) const {
        return point(x-G.x,y-G.y);
    }
};
ll dot(point a,point b){
    return a.x*b.x+a.y*b.y;
}
ll cross(point a,point b){
    return a.x*b.y-a.y*b.x;
}
double len(point a){
    return sqrt(a.x*a.x+a.y*a.y);
}
struct node{
    ll type,a,b,c,ans,last;
}; 
node work[500005];

ll f(point a,int t){
    return a.x*work[t].a+a.y*work[t].b+work[t].c;
}

point p[500005];
int down,up;
void convex_closure(point s[],int k){
    int i,j;
    sort(s+1,s+1+k);
    down=up=0; 
    for(i=1;i<=k;i++){
        while(up>1&&cross(p[up-1]-p[up-2],s[i]-p[up-2])<=0)up--;
        p[up++]=s[i];
    }
    down=up;
    for(i=k-1;i;i--){
        while(up>down&&cross(p[up-1]-p[up-2],s[i]-p[up-2])<=0)up--;
        p[up++]=s[i];
    }
    if(k>1)up--;
}
ll getmax(int l,int r,int t){
    if(r<l)return -inf;
    ll maxn=-inf;
    maxn=max(f(p[l],t),f(p[r],t));
    int i,j,k;
    for(;;){
        if(r-l<=6){
            for(i=l;i<=r;i++){
                maxn=max(maxn,f(p[i],t));
            }
            return maxn;
        }
        int lmid=l+(r-l+1)/3,rmid=lmid+(r-l+1)/3;
        if(f(p[lmid],t)>f(p[rmid],t))r=rmid;
        else l=lmid;
    }
}
ll getmin(int l,int r,int t){
    if(r<l)return inf;
    ll minn=inf;
    minn=min(f(p[l],t),f(p[r],t));
    int i,j,k;
    for(;;){
        if(r-l<=6){
            for(i=l;i<=r;i++){
                minn=min(minn,f(p[i],t));
            }
            return minn;
        }
        int lmid=l+(r-l+1)/3,rmid=lmid+(r-l+1)/3;
        if(f(p[lmid],t)<f(p[rmid],t))r=rmid;
        else l=lmid;
    }
}
point temp[200005];
void cdq(int l,int r){
    if(l==r)return;
    int mid=(l+r)>>1;
    //cout<<"cdq("<<l<<","<<r<<")"<<endl;
    cdq(l,mid);cdq(mid+1,r);
    int i,j,k;
    k=0;
    for(i=l;i<=mid;i++){
        if(work[i].type==1)temp[++k]=point(work[i].a,work[i].b);
    } 
    if(k==0)return;
    convex_closure(temp,k);
    for(i=mid+1;i<=r;i++){
        if(work[i].type==2){
            ll maxn,minn;
            maxn=max(getmax(0,down-1,i),getmax(down,up-1,i));
            minn=min(getmin(0,down-1,i),getmin(down,up-1,i));
            if(maxn>0)maxn=1;
            if(maxn<0)maxn=-1;
            if(minn>0)minn=1;
            if(minn<0)minn=-1;
            if(maxn==0||minn==0||(minn*maxn)<0||(maxn*work[i].last)<0)work[i].ans=-1;
            work[i].last=maxn;
        }
    }
}
int main(){
    int i,j,k;
    cin>>n>>q;
    for(i=1;i<=n;i++){
        work[i].type=1;
        _read(work[i].a);_read(work[i].b);
    }
    for(i=n+1;i<=n+q;i++){
        _read(k);
        if(k==1){
            work[i].type=1;
            _read(work[i].a);_read(work[i].b);
        }
        if(k==2){
            work[i].type=2;
            _read(work[i].a);_read(work[i].b);_read(work[i].c);
            work[i].c=-work[i].c;
        }
    }
    cdq(1,n+q);
    for(i=n+1;i<=n+q;i++){
        if(work[i].type==2){
            if(work[i].ans==-1)puts("NO");
            else puts("YES");
        }
    }
}
资源下载链接为: https://pan.quark.cn/s/140386800631 通用大模型文本分类实践的基本原理是,借助大模型自身较强的理解和推理能力,在使用时需在prompt中明确分类任务目标,并详细解释每个类目概念,尤其要突出类目间的差别。 结合in-context learning思想,有效的prompt应包含分类任务介绍及细节、类目概念解释、每个类目对应的例子和待分类文本。但实际应用中,类目和样本较多易导致prompt过长,影响大模型推理效果,因此可先通过向量检索缩小范,再由大模型做最终决策。 具体方案为:离线时提前配置好每个类目的概念及对应样本;在线时先对给定query进行向量召回,再将召回结果交给大模型决策。 该方法不更新任何模型参数,直接使用开源模型参数。其架构参考GPT-RE并结合相关实践改写,加入上下文学习以提高准确度,还使用BGE作为向量模型,K-BERT提取文本关键词,拼接召回的相似例子作为上下文输入大模型。 代码实现上,大模型用Qwen2-7B-Instruct,Embedding采用bge-base-zh-v1.5,向量库选择milvus。分类主函数的作用是在向量库中召回相似案例,拼接prompt后输入大模型。 结果方面,使用ICL时accuracy达0.94,比bert文本分类的0.98低0.04,错误类别6个,处理时添加“家居”类别,影响不大;不使用ICL时accuracy为0.88,错误58项,可能与未修改prompt有关。 优点是无需训练即可有较好结果,例子优质、类目界限清晰时效果更佳,适合绕通用大模型api打造工具;缺点是上限不高,仅针对一个分类任务部署大模型不划算,推理速度慢,icl的token使用多,用收费api会有额外开销。 后续可优化的点是利用key-bert提取的关键词,因为核心词语有时比语意更重要。 参考资料包括
内容概要:本文详细介绍了哈希表及其相关概念和技术细节,包括哈希表的引入、哈希函数的设计、冲突处理机制、字符串哈希的基础、哈希错误率分析以及哈希的改进与应用。哈希表作为一种高效的数据结构,通过键值对存储数据,能够快速定位和检索。文中讨论了整数键值和字符串键值的哈希方法,特别是字符串哈希中的多项式哈希及其优化方法,如双哈希和子串哈希的快速计算。此外,还探讨了常见的冲突处理方法——拉链法和闭散列法,并提供了C++实现示例。最后,文章列举了哈希在字符串匹配、最长回文子串、最长公共子字符串等问题中的具体应用。 适合人群:计算机科学专业的学生、算法竞赛选手以及有一定编程基础并对数据结构和算法感兴趣的开发者。 使用场景及目标:①理解哈希表的工作原理及其在各种编程任务中的应用;②掌握哈希函数的设计原则,包括如何选择合适的模数和基数;③学会处理哈希冲突的方法,如拉链法和闭散列法;④了解并能运用字符串哈希解决实际问题,如字符串匹配、回文检测等。 阅读建议:由于哈希涉及较多数学知识和编程技巧,建议读者先熟悉基本的数据结构和算法理论,再结合代码实例进行深入理解。同时,在实践中不断尝试不同的哈希策略,对比性能差异,从而更好地掌握哈希技术。
### CDQ分治法的基本概念 CDQ分治是一种基于分治思想的算法策略,最初由陈丹琦引入国内算法竞赛领域,因此被称为CDQ分治。该方法的核心思想是将问题划分为若干子问题,并递归地解决这些子问题。在解决子问题的同时,处理左半部分对右半部分的影响,从而逐步构建最终解[^1]。 CDQ分治的关键特征在于其递归结构:首先递归处理左半区间和右半区间的子问题,随后处理左区间对右区间的影响。这种策略通常利用排序来制造单调性,从而降低计算复杂度[^2]。 ### CDQ分治法的工作原理 CDQ分治的工作流程可以分为以下三个步骤: 1. **划分**:将原始问题划分为两个子问题,通常是对数组进行二分,分别处理左半区间和右半区间。 2. **处理**:计算左半区间对右半区间的影响。这一过程通常涉及对数据进行排序,以利用单调性减少重复计算[^3]。 3. **合并**:递归地处理右半区间的问题,并将结果整合。 以归并排序求逆序对为例,在合并两个子区间的过程中,需要计算左边区间对右边区间的影响。具体来说,当从右子区间中取出一个元素时,统计左边区间中比该元素大的元素数量,从而得到逆序对的个数。这一过程体现了CDQ分治的核心思想[^3]。 ### CDQ分治法的应用场景 CDQ分治广泛应用于解决多维偏序问题,例如二维偏序、三维偏序等。对于二维偏序问题,可以通过CDQ分治结合排序和树状数组来高效求解。在处理三维偏序问题时,CDQ分治通常与树状数组结合,以达到 $ O(n \log^2 n) $ 的时间复杂度[^5]。 此外,CDQ分治也常用于动态规划优化问题。例如,在求解最长递增子序列问题时,可以利用CDQ分治处理条件约束,例如 $ f_i = \max\{f_j + 1 \mid j < i, r_j \le a_i, a_j \le l_i\} $,其中 $ f_i $ 表示以第 $ i $ 个元素为结尾的最长子序列长度[^4]。 ### CDQ分治法的实现示例 以下是一个基于CDQ分治的伪代码示例,用于处理二维偏序问题: ```python def cdq_divide(l, r): if l == r: return mid = (l + r) // 2 cdq_divide(l, mid) cdq_divide(mid + 1, r) # 处理左区间对右区间的影响 # 例如:统计左区间中满足条件的元素对右区间的影响 # ... # 合并两个子区间并排序 # ... ``` 在具体实现中,通常需要结合归并排序的思想,以确保数据在分治过程中保持有序,从而提高效率[^5]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值