2021“MINIEYE杯”中国大学生算法设计超级联赛(1)zoto(二维数颜色)

zoto

Code1

树状数组套动态开点权值线段树
效仿HH的项链,维护右端点,询问需要排序

#include<bits/stdc++.h>
using namespace std;
template <class T=int> T rd()
{
    T res=0;
    char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while( isdigit(ch)) res=(res<<1)+(res<<3)+(ch^48),ch=getchar();
    return res;
}
const int N=100010;
int a[N],n,m,ans[N],last[N];
struct nodeq
{
    int l,r,L,R,id;
    bool operator<(const nodeq& o)const
    {
        return r<o.r;
    }
}q[N];
struct node
{
    int l,r,v;
}tree[N*200];
int rt[N],cnt;
void update(int &u,int l,int r,int pos,int v)
{
    if(!u) tree[u=++cnt]={0,0,0};
    tree[u].v+=v;
    if(l==r) return;
    int mid=l+r>>1;
    if(pos<=mid) update(tree[u].l,l,mid,pos,v);
    if(pos>mid)  update(tree[u].r,mid+1,r,pos,v);
}
int query(int u,int l,int r,int L,int R)
{
    if(!u) return 0;
    if(L<=l&&r<=R) return tree[u].v;
    int mid=l+r>>1;
    int v=0;
    if(L<=mid) v+=query(tree[u].l,l,mid,L,R);
    if(R>mid) v+=query(tree[u].r,mid+1,r,L,R);
    return v;
}
int lowbit(int x){return x&-x;}
void add(int k,int pos,int v)
{
    for(;k<=n;k+=lowbit(k)) update(rt[k],0,100000,pos,v);
}
int ask(int k,int L,int R)
{
    int ans=0;
    for(;k;k-=lowbit(k)) ans+=query(rt[k],0,100000,L,R);
    return ans;
}

void init()
{
    for(int i=1;i<=100000;i++) last[i]=0;
    for(int i=1;i<=n;i++) rt[i]=0;cnt=0;
    for(int i=0;i<=cnt;i++) tree[i].l=tree[i].r=tree[i].v=0;
}
int main()
{
    int Tc=rd();
    while(Tc--)
    {
        init();
        n=rd(),m=rd();
        for(int i=1;i<=n;i++) a[i]=rd();
        for(int i=1;i<=m;i++)
        {
            int x0=rd(),y0=rd(),x1=rd(),y1=rd();
            q[i]={x0,x1,y0,y1,i};
        }
        sort(q+1,q+1+m);
        int k=1;
        for(int i=1;i<=m;i++)
        {
            for(;k<=q[i].r;k++)
            {
                int v=a[k];
                if(last[v]) add(last[v],v,-1);
                add(k,v,1);
                last[v]=k;
            }
          ans[q[i].id]=ask(q[i].r,q[i].L,q[i].R)-ask(q[i].l-1,q[i].L,q[i].R);
        }
        for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
    }
}
Code2

树状数组套动态开点权值线段树
校dl的写法,不需要对询问进行排序

#include<bits/stdc++.h>
using namespace std;
template <class T=int> T rd()
{
    T res=0;
    char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while( isdigit(ch)) res=(res<<1)+(res<<3)+(ch^48),ch=getchar();
    return res;
}
const int N=100010;
int a[N],n,m,ans[N],last[N];
struct nodeq
{
    int l,L,R,id;
};
vector<nodeq> q[N];
struct node
{
    int l,r,v;
}tree[N*200];
int rt[N],cnt,lim;
void update(int &u,int l,int r,int pos,int v)
{
    if(!u) u=++cnt;
    tree[u].v+=v;
    if(l==r) return;
    int mid=l+r>>1;
    if(pos<=mid) update(tree[u].l,l,mid,pos,v);
    if(pos>mid)  update(tree[u].r,mid+1,r,pos,v);
    //tree[u].v=tree[tree[u].l].v+tree[tree[u].r].v;
}
int query(int u,int l,int r,int L,int R)
{
    if(!u) return 0;
    if(L<=l&&r<=R) return tree[u].v;
    int mid=l+r>>1;
    int v=0;
    if(L<=mid) v+=query(tree[u].l,l,mid,L,R);
    if(R>mid) v+=query(tree[u].r,mid+1,r,L,R);
    return v;
}
int lowbit(int x){return x&-x;}
void add(int k,int pos,int v)
{
    for(;k<=n;k+=lowbit(k)) update(rt[k],0,lim,pos,v);
}
int ask(int k,int L,int R)
{
    int ans=0;
    for(;k;k-=lowbit(k)) ans+=query(rt[k],0,lim,L,R);
    return ans;
}
void init()
{
    
    for(int i=0;i<=lim;i++) last[i]=0;
    for(int i=0;i<=n;i++) rt[i]=0;
    for(int i=0;i<=cnt;i++) tree[i].l=tree[i].r=tree[i].v=0;
    cnt=0;
    for(int i=0;i<=n;i++) q[i].clear();
}
int main()
{
    int Tc=rd();
    while(Tc--)
    {
        init();
        n=rd(),m=rd();
        for(int i=1;i<=n;i++) a[i]=rd();
        lim=*max_element(a+1,a+1+n);
        
        for(int i=1;i<=m;i++)
        {
            int x0=rd(),y0=rd(),x1=rd(),y1=rd();
            q[x1].push_back({x0,y0,y1,i});
        }
        for(int i=1;i<=n;i++)
        {
            if(last[a[i]]) add(last[a[i]],a[i],-1);
            add(i,a[i],1);last[a[i]]=i;
            for(auto t:q[i])
                ans[t.id]=ask(i,t.L,t.R)-ask(t.l-1,t.L,t.R);
        }
        for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
    }
}
Code3

std写法

首先来看一个子问题:给一个数组,初始所有位置全是0,每次单点加一或减一,询问区间有多少个位置不为0。也许你很快会给出一个修改O(logn)查询O(logn)的优秀做法。那么如果强制规定修改O(1),查询能做到什么效率?我们去考虑分块,维护两个数组num[i]和sum[i]分别维护第i位置上的值,以及第i块内的值不为0的位置个数。那么每次单点修改只需要修改num[i]和sum[i/block_size]两个位置的值,然后查询的时候需要O(sqrtn)效率的去查询区间所覆盖的完整块的值以及区间两端散块的值(散块值可以没有)。这显然是一个修改O(1),查询O(sqrtn)的做法。
然后考虑这个题,我们发现可以用莫队去维护询问的区间,即x坐标。y(fx)维度单独拎出来就变成了上述子问题。我们会神奇的发现,这样的复杂度是:O(nsqrt(n)1+m1sqrt(n))的,莫队单次修改时O(sqrtn)的复杂度遇上了值域上O(1)的修改,莫队单次查询时O(1)的复杂度遇上了值域查询时O(sqrt(n))的复杂度。

#include<bits/stdc++.h>

using namespace std;
using ll=long long;
template <class T=int> T rd()
{
    T res=0;
    char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while( isdigit(ch)) res=(res<<1)+(res<<3)+(ch^48),ch=getchar();
    return res;
}
const int N=100010;
int a[N],n,m;

int b[N],sz;
struct nodeq
{
    int l,r,L,R,id;
    bool operator<(const nodeq&o)const
    {
        if(b[l]==b[o.l])
        {
            if(b[l]&1)
                return r<o.r;
            else
                return r>o.r;
        }
        return b[l]<b[o.l];
    }
}q[N];
int num[N],sum[N],ans[N];
void add(int k){if(++num[k]==1) sum[b[k]]++;}
void sub(int k){if(--num[k]==0) sum[b[k]]--;}
int query(int k)
{
    int res=0;
    for(int i=1;i<b[k];i++) res+=sum[i];
    for(int i=(b[k]-1)*sz+1;i<=k;i++) res+=(num[i]>=1);
    return res;
}
int main()
{
    int Tc=rd();
    while(Tc--)
    {
        n=rd(),m=rd(),sz=313;
        for(int i=1;i<=n;i++) a[i]=rd();
        for(int i=1;i<=n;i++) sum[i]=num[i]=0;
        for(int i=1;i<=n;i++) b[i]=(i-1)/sz+1;
        for(int i=1;i<=m;i++)
        {
            int x0=rd(),y0=rd(),x1=rd(),y1=rd();
            q[i]={x0,x1,y0,y1,i};
        }
        sort(q+1,q+1+m);
        
        int l=1,r=0;
        for(int i=1;i<=m;i++)
        {
            while(l<q[i].l) sub(a[l++]);
            while(l>q[i].l) add(a[--l]);
            while(r<q[i].r) add(a[++r]);
            while(r>q[i].r) sub(a[r--]);
            ans[q[i].id]=query(q[i].R)-query(q[i].L-1);
        }
        for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
        
    }
}

Code4

把种类查询转换偏序查询的常规操作:指向上一个出现的位置当且仅当上一次出现的位置在区间外面时计算贡献

cdq分治+bit
对于每个询问的右端点也看作一个时间轴,如果该点可能对询问产生贡献,那么下一个这个点的横坐标严格大于询问右端点。
a 1 ≤ a 1 b 1 ≤ b 2 c 1 > c 2 a_1\leq a_1\\ b_1\leq b_2 \\c_1>c_2 a1a1b1b2c1>c2
cdq分治转化为平面二维数点,然后差分+bit即可统计。


对于每个询问的左端点也看作一个时间轴,如果该点可能对询问产生贡献,那么前一个这个点的横坐标严格小于询问左端点。

本质都种类 → \to 偏序

#include<bits/stdc++.h>
using namespace std;
template <class T=int> T rd()
{
    T res=0;
    char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while( isdigit(ch)) res=(res<<1)+(res<<3)+(ch^48),ch=getchar();
    return res;
}
const int N=100010;
int a[N],n,m,ans[N],last[N];
int ne[N],pos[N];
struct nodeq
{
    int op;
    int a,b,c;
    int fg,id;
}q[N<<4];
int fw[N];
int lowbit(int x){return x&-x;}
void update(int k,int v){for(;k<=n+1;k+=lowbit(k)) fw[k]+=v;}
int qsum(int k){int v=0;for(;k;k-=lowbit(k)) v+=fw[k];return v;}

void solve(int l,int r)
{
    if(l>=r) return;
    int mid=l+r>>1;
    solve(l,mid),solve(mid+1,r);
    int i=l;
    for(int j=mid+1;j<=r;j++)
    {
        while(i<=mid&&q[i].b<=q[j].b) 
        {
            if(q[i].op==0) update(q[i].c,1);
            i++;
        }
        if(q[j].op==1) ans[q[j].id]+=q[j].fg*(qsum(n+1)-qsum(q[j].c));

    }
    while(i>l) 
    {
        --i;
        if(q[i].op==0) update(q[i].c,-1);
    }
    inplace_merge(q+l,q+mid+1,q+r+1,[](const nodeq&x,const nodeq&y){return x.b<y.b||x.b==y.b&&x.a<y.a;});
}
int main()
{
    int Tc=rd();
    while(Tc--)
    {
        
        n=rd(),m=rd();
        for(int i=1;i<=n;i++) a[i]=rd();
        for(int i=1;i<=m;i++) ans[i]=0;
        for(int i=0;i<=100000;i++) pos[a[i]]=n+1;
        
        for(int i=n;i>=1;i--) 
        {
            ne[i]=pos[a[i]];
            pos[a[i]]=i;
        }
        
        int cnt=0;
        for(int i=1;i<=n;i++) q[++cnt]={0,i,a[i],ne[i]};
        for(int i=1;i<=m;i++)
        {
            int x0=rd(),y0=rd(),x1=rd(),y1=rd();
            q[++cnt]={1,x0-1,y0-1,x1,1,i};
            q[++cnt]={1,x0-1,y1,x1,-1,i};
            q[++cnt]={1,x1,y0-1,x1,-1,i};
            q[++cnt]={1,x1,y1,x1,1,i};
        }
        sort(q+1,q+1+cnt,[](const nodeq&x,const nodeq&y){return x.a<y.a||x.a==y.a&&x.b<y.b;});
        solve(1,cnt);
        for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
    }
}
Code5

和Code4基本一样

#include<bits/stdc++.h>
using namespace std;
template <class T=int> T rd()
{
    T res=0;
    char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while( isdigit(ch)) res=(res<<1)+(res<<3)+(ch^48),ch=getchar();
    return res;
}
const int N=100010;
int a[N],n,m,ans[N];
int last[N],pos[N];
struct nodeq
{
    int op;
    int a,b,c;
    int fg,id;
}q[N<<4];
int fw[N];
int lowbit(int x){return x&-x;}
void update(int k,int v){if(!k) return fw[k]+=v,void();for(;k<=n;k+=lowbit(k)) fw[k]+=v;}
int qsum(int k){int v=fw[0];for(;k;k-=lowbit(k)) v+=fw[k];return v;}

void solve(int l,int r)
{
    if(l>=r) return;
    int mid=l+r>>1;
    solve(l,mid),solve(mid+1,r);
    int i=l;
    for(int j=mid+1;j<=r;j++)
    {
        while(i<=mid&&q[i].b<=q[j].b) 
        {
            if(q[i].op==0) update(q[i].c,1);
            i++;
        }
        if(q[j].op==1) ans[q[j].id]+=q[j].fg*qsum(q[j].c);
    }
    while(i>l) 
    {
        --i;
        if(q[i].op==0) update(q[i].c,-1);
    }
    inplace_merge(q+l,q+mid+1,q+r+1,[](const nodeq&x,const nodeq&y){return x.b<y.b||x.b==y.b&&x.a<y.a;});
}
int main()
{
    int Tc=rd();
    while(Tc--)
    {
        
        n=rd(),m=rd();
        for(int i=1;i<=n;i++) a[i]=rd();
        for(int i=1;i<=m;i++) ans[i]=0;
        for(int i=0;i<=100000;i++) pos[a[i]]=0;
        
        for(int i=1;i<=n;i++) 
        {
            last[i]=pos[a[i]];
            pos[a[i]]=i;
        }
        
        int cnt=0;
        for(int i=1;i<=n;i++) q[++cnt]={0,i,a[i],last[i]};
        for(int i=1;i<=m;i++)
        {
            int x0=rd(),y0=rd(),x1=rd(),y1=rd();
            q[++cnt]={1,x0-1,y0-1,x0-1,1,i};
            q[++cnt]={1,x0-1,y1,x0-1,-1,i};
            q[++cnt]={1,x1,y0-1,x0-1,-1,i};
            q[++cnt]={1,x1,y1,x0-1,1,i};
        }
        sort(q+1,q+1+cnt,[](const nodeq&x,const nodeq&y){return x.a<y.a||x.a==y.a&&x.b<y.b;});
        solve(1,cnt);
        for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
    }
}

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值