【整体二分/树套树】BZOJ3833 [Poi2014]Solar lamps

【题目】
原题地址
n n 盏灯,每盏灯能照到的角度范围是相同的,第i盏灯在第 i i 秒或者被ki盏灯照到后都会亮起,问所有灯都在什么时刻亮起。

【题目分析】
看上去很玄学,实际上重要的点在思路的转化。我最喜欢分治了
我最喜欢薇尔莉特!!

【解题思路】
首先第一眼看上去像计算几何qwq,不过因为所有灯朝向一样,所以我们转一下坐标轴就行了。
我们把 (xi,yi) ( x i , y i ) 变成 (ai,bi) ( a i , b i ) 使得

ai×x1+bi×x2=xiai×y1+bi×y2=yi a i × x 1 + b i × x 2 = x i a i × y 1 + b i × y 2 = y i

然后可以得到:
ai=xi×y2yi×x2x1×y2x2×y1bi=xi×y1yi×x1x1×y2x2×y1 a i = x i × y 2 − y i × x 2 x 1 × y 2 − x 2 × y 1 b i = x i × y 1 − y i × x 1 x 1 × y 2 − x 2 × y 1

这些坐标分母一样,所以可以丢掉。
转化问题后,我们发现这就是就是按顺序查询一个点左下角的区域内第 ki k i 小的数是多少(记为 t t )。
然后有ansi=min(i,t),特别地,如果没有 ki k i 个灯泡, ansi=i a n s i = i

考虑怎么维护这个东西?

我们可以写树套树维护一下插入操作,以往都是要二分然后再查询的,这样时间复杂度是 O(log3n) O ( l o g 3 n ) 的,显然不可以接受….
但是这题只需要查 k k 大,所以我们可以反过来,以ans为第一关键字建线段树,然后对 a a 坐标建平衡树,这样就可以省去二分了。
想用带修主席树?不好意思卡内存呢。

查询操作我们就可以二分答案了。
具体做法就是从线段树根结点开始,先看左儿子所套的平衡树里边a坐标小于等于 ai a i 的结点个数。
设有 u u 个,如果uk[i],说明答案应该在 [l,mid] [ l , m i d ] 之间,然后递归处理。
否则答案就应该在 [mid+1,r] [ m i d + 1 , r ] 之间,然后递归处理。
直到 l=r l = r 的时候,便可确定 ans[i]=min(i,l) a n s [ i ] = m i n ( i , l )
查询操作中,我们要访问 O(logn) O ( l o g n ) 个线段树结点,在每个线段树节点中,都要 O(logn) O ( l o g n ) 地查找相关点的个数。
所以查询操作的时间复杂度也是 O(log2n) O ( l o g 2 n ) 的。

然后有 O(n) O ( n ) 个灯泡,所以总时间复杂度是 O(nlog2n) O ( n l o g 2 n ) 的。
每个灯泡的信息会被插入到 O(logn) O ( l o g n ) 个线段树所套的平衡树里,
所以空间复杂度是 O(nlogn) O ( n l o g n ) 的。

以上,太麻烦了,我不想写。对于我来说,正确的操作姿势应该是整体二分。

直接二分时间 [L,R] [ L , R ] 同时 [l,r] [ l , r ] 为在此段时间内点亮的灯,取 Mid=L+R2 M i d = L + R 2
[l,r] [ l , r ] 划分为 [l,mid] [ l , m i d ] [mid+1,r] [ m i d + 1 , r ] 表示在 Mid M i d 前点亮的灯与在Mid后点亮的灯,递归求解即可。

那么现在问题是如何求一盏灯是否在 Mid M i d 前点亮,关键在于求一盏灯的左下角的灯的数目。
我们直接用扫描线做就行了:将灯按照 x x 坐标排序,用树状数组维护y坐标,横向依次扫描就可以完成了。
如果求得的左下角的灯的个数大于等于 k k 个,或者灯的编号大于等于Mid,则把它归到 [l,mid] [ l , m i d ] 的区间。

然后就是这样,写分治不仅代码简单,而且常数小,何乐而不为呢?
(AC时速度第4,代码2kb,写数据结构的话速度和代码量都是两倍左右)
~~强烈鄙视这种文末一定要带换行符的PE!!!~~

【参考代码】

整体二分
#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const LL INF=2e9;
const int N=2e5+10;
int n,tx,ty,txx,tyy;
LL X[N],Y[N],dat[N];

struct Tnode
{
    int x,y,k,ans,id;
    bool operator < (const Tnode &A)const
    {
        if(x==A.x)
            return y<A.y;
        return x<A.x;
    }
};
Tnode a[N];

struct Tbit
{
    int tr[N];
    int lowbit(int x){
        return x&(-x);
    }

    void add(int x,int v)
    {
        for(;x<=n;x+=lowbit(x))
            tr[x]+=v;
    }

    int query(int x)
    {
        int ret=0;
        for(;x;x-=lowbit(x))
            ret+=tr[x];
        return ret; 
    }
};
Tbit BIT;

bool cmp(Tnode A,Tnode B)
{
    return A.id<B.id;
}

void update(int &x,int &y)
{
    int mx=max(abs(x),abs(y));
    int d=((LL)INF+mx-1)/mx;
    x*=d;y*=d;
    ++x;
}

void init()
{
    for(int i=1;i<=n;++i)
        dat[i]=X[i];
    sort(dat+1,dat+n+1);
    int sz=unique(dat+1,dat+n+1)-dat;
    for(int i=1;i<=n;++i)
        a[i].x=lower_bound(dat+1,dat+sz+1,X[i])-dat;

    for(int i=1;i<=n;++i)
        dat[i]=Y[i];
    sort(dat+1,dat+n+1);
    sz=unique(dat+1,dat+n+1)-dat;
    for(int i=1;i<=n;++i)
        a[i].y=lower_bound(dat+1,dat+sz+1,Y[i])-dat;
}

void solve(int L,int R,int l,int r)
{
    if(L==R)
    {
        for(int i=l;i<=r;++i)
            a[i].ans=L;
        return;
    }
    int Mid=(L+R)>>1,mid=l-1;
    sort(a+l,a+r+1);

    for(int i=l;i<=r;++i)
    {
        int res=BIT.query(a[i].y);
        if(a[i].id<=Mid || res>=a[i].k)
        {
            BIT.add(a[i].y,1);
            ++mid;swap(a[mid],a[i]);
        }
        else
            a[i].k-=res;
    }

    for(int i=l;i<=mid;++i)
        BIT.add(a[i].y,-1);

    if(l<=mid)
        solve(L,Mid,l,mid);
    if(r>mid)
        solve(Mid+1,R,mid+1,r);
}

int main()
{
//  freopen("BZOJ3833.in","r",stdin);
//  freopen("BZOJ3833.out","w",stdout);

    scanf("%d%d%d%d%d",&n,&tx,&ty,&txx,&tyy);
    if(1ll*tx*tyy==1ll*ty*txx)
        update(tx,ty);
    if(1ll*tx*tyy<1ll*ty*txx)
        swap(tx,txx),swap(ty,tyy);
    for(int i=1;i<=n;++i)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        a[i].id=i;
        X[i]=1ll*x*tyy-1ll*txx*y;
        Y[i]=1ll*tx*y-1ll*x*ty;
    }   
    for(int i=1;i<=n;++i)
        scanf("%d",&a[i].k);
    init();
    solve(1,n,1,n);
    sort(a+1,a+n+1,cmp);
    for(int i=1;i<=n;++i)
        printf("%d%c",a[i].ans,i==n?'\n':' ');

    return 0;
}

然后树套树的没写,在这里放一份大佬的代码qwq。来源

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
//#include<ctime>
#define N 200010
using namespace std;
struct ppp
{
    long long x,y;
    int num,k;
}b[N];
bool cmp(ppp x,ppp y)
{
    if(x.x!=y.x) return x.x<y.x;
    return x.y<y.y;
}
long long ax[N],ay[N];
map<long long,int>px,py;
int ans[N];
int nx,ny;
int l[N<<2],r[N<<2],rt[N<<2];
int ch[N*20][2],siz[N*20],a[N*20],fa[N*20],ti[N*20],cnt;
inline void pup(int x){siz[x]=siz[ch[x][0]]+ti[x]+siz[ch[x][1]];}
void rotate(int x,int &k)
{
    int y=fa[x],z=fa[y];
    int l=0,r;
    if(ch[y][1]==x) l=1;r=l^1;
    if(y==k) k=x;
    else
    {
        if(ch[z][0]==y) ch[z][0]=x;
        else ch[z][1]=x;
    }
    fa[ch[x][r]]=y;
    fa[y]=x;
    fa[x]=z;
    ch[y][l]=ch[x][r];
    ch[x][r]=y;
    pup(y);//pup(x);
}
void splay(int x,int &k)
{
    while(x!=k)
    {
        int y=fa[x],z=fa[y];
        if(y!=k)
        {
            if(ch[z][0]==y^ch[y][0]==x) rotate(x,k);
            else rotate(y,k);
        }
        rotate(x,k);
    }
}
void add(int &k,int x)
{
    if(!k)
    {
        cnt++;k=cnt;
        siz[cnt]=ti[cnt]=1;
        a[cnt]=x;
        return;
    }
    int z,p=k;
    while(p)
    {
        z=p;
        siz[z]++;
        if(x==a[z])
        {
            ti[z]++;
            return;
        }
        if(x>a[z])
        p=ch[z][1];
        else p=ch[z][0];
    }
    ti[++cnt]=1;
    siz[cnt]=1;
    a[cnt]=x;
    fa[cnt]=z;
    if(x>a[z])
    ch[z][1]=cnt;
    else ch[z][0]=cnt;
    splay(cnt,k);
}
int numgetrank(int k,int x,bool f)
{
    if(!k) return 0;
    if(a[k]==x) 
    {
        if(!f) return siz[ch[k][0]]+ti[k];
        return ti[k];
    }
    if(x<a[k]) return numgetrank(ch[k][0],x,f);
    else
    {
        if(!f) return numgetrank(ch[k][1],x,f)+siz[ch[k][0]]+ti[k];
        return numgetrank(ch[k][1],x,f);
    }
}
inline void build(int now,int ll,int rr)
{
    l[now]=ll;r[now]=rr;
    if(ll==rr) return;
    int mid=(ll+rr)>>1;
    build(now<<1,ll,mid);
    build(now<<1|1,mid+1,rr); 
}
inline int check(int now,int v,int k,bool f)
{
    if(l[now]==r[now]) return l[now];
    int t=numgetrank(rt[now<<1],v,f);
    if(k<=t) return check(now<<1,v,k,f);
    else return check(now<<1|1,v,k-t,f);
}
inline void change(int now,int x,int v)
{
    add(rt[now],v);
    if(l[now]==r[now]) return;
    int mid=(l[now]+r[now])>>1;
    if(x<=mid) change(now<<1,x,v);
    else change(now<<1|1,x,v);
}
const int ___=1<<15;
char ZZ[___|1],*_=ZZ,*Z=ZZ,Z_[___],_Z;
#define getc() (_==Z&&(Z=(_=ZZ)+fread(ZZ,1,___,stdin),_==Z)?0:*_++)
inline int read()
{
    int x=0,f=1;
    char c=getc();
    for(;c<48||c>57;c=getc())if(c==45)f=-1;
    for(;c>=48&&c<=57;)x=x*10+c-48,c=getc();
    return x*f;
}
inline long long readll()
{
    long long x=0,f=1;
    char c=getc();
    for(;c<48||c>57;c=getc())if(c==45)f=-1;
    for(;c>=48&&c<=57;)x=x*10+c-48,c=getc();
    return x*f;
}
long long X1,X2,Y1,Y2;
int main()
{
    int n;
    int tot=0,st=clock();
    n=read();
    long long x,y;
    X1=readll();Y1=readll();X2=readll();Y2=readll();
    bool fl=false; 
    if(X1*Y2==Y1*X2)
    {
        fl=true;
        X2=-Y1;Y2=X1;
    }
    long long tmp=1;
    if(X1*Y2-Y1*X2<0) tmp=-1;
    int i,j;
    for(i=1;i<=n;i++)
    {
        x=readll();y=readll();
        b[i].x=tmp*(x*Y2-y*X2);
        b[i].y=tmp*(y*X1-x*Y1);
        b[i].num=i;
        ax[i]=b[i].x;
        ay[i]=b[i].y;
    }
    for(i=1;i<=n;i++)
    b[i].k=read();
    sort(ax+1,ax+n+1);
    ax[0]=707185547;
    for(i=1;i<=n;i++)
    if(ax[i]!=ax[i-1])
    {
        nx++;
        px[ax[i]]=nx;
    }
    sort(ay+1,ay+n+1);
    ay[0]=707185547;
    for(i=1;i<=n;i++)
    if(ay[i]!=ay[i-1])
    {
        ny++;
        py[ay[i]]=ny;
    }
    sort(ax+1,ax+n+1);
    sort(ay+1,ay+n+1);
    int sx=unique(ax+1,ax+n+1)-ax;
    int sy=unique(ay+1,ay+n+1)-ay;
    for (int i = 1; i <= n; i ++)
    {
        int u=lower_bound(ax+1,ax+sx,b[i].x)-ax;
        int v=lower_bound(ay+1,ay+sy,b[i].y)-ay;
        b[i].x=u,b[i].y=v;
    }
    sort(b+1,b+n+1,cmp);
    build(1,1,n);
    for(i=1;i<=n;i++)
    {
        tmp=numgetrank(rt[1],b[i].y,fl);
        if(tmp<b[i].k) ans[b[i].num]=b[i].num;
        else ans[b[i].num]=min(check(1,b[i].y,b[i].k,fl),b[i].num);
    //  st=clock();
        change(1,ans[b[i].num],b[i].y);
    //  tot+=clock()-st;
    }
    for(i=1;i<n;i++)
    printf("%d ",ans[i]);
    printf("%d",ans[n]);
//  cout<<tot;
}

【总结】
题目的转化很巧妙(其实也还好)
主要是这个坐标系的转化,牢记在心。
为了薇尔莉特!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值