其他分享
首页 > 其他分享> > NC19427 换个角度思考

NC19427 换个角度思考

作者:互联网

在这里插入图片描述
一个很有意思的模板题
思路:
有三种方法可以做
一种是树状数组+离线的方式
我们可以先将题目所给a[i]建一个结构体储存它的下标之后对值进行排序
然后对所有询问的k值进行排序
因为你要求的是区间中小于等于k的有多少个
那么我们可以对于小于等于k的a[i]全部先加入到树状数组里面
之后求的时候直接求区间中的值(因为你加进去的全部都是小于等于k的)
最后按原来的询问顺序输出即可(看了一下运行速度这个方法应该是最快的)
代码:

#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=1e5+10;
ll c[N];
struct node1
{
    int v;
    int i;
} a[N];
struct node2
{
    int l,r,k,i;
} q[N];
ll ans[N];
int n,m;
int lower_bit(int x){return x&(-x);}
ll getsum(int x)
{
    ll cnt=0;
    while(x>0)
    {
        cnt+=c[x];
        x-=lower_bit(x);
    }
    return cnt;
}
void add(int x,int num)
{
    while(x<=n)
    {
        c[x]+=num;
        x+=lower_bit(x);
    }
    return;
}
int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1; i<=n; i++)cin>>a[i].v,a[i].i=i;
    sort(a+1,a+1+n,[](node1 b,node1 c){return b.v<c.v;});//按值进行排序
    for(int i=1; i<=m; i++)
    {
        cin>>q[i].l>>q[i].r>>q[i].k;
        q[i].i=i;
    }
    sort(q+1,q+1+m,[](node2 b,node2 c){return b.k<c.k;});//按k进行排序
    int j=1;
    for(int i=1; i<=m; i++)
    {
        while(j<=n&&a[j].v<=q[i].k)
        {
            add(a[j].i,1);
            j++;
        }
        ans[q[i].i]=getsum(q[i].r)-getsum(q[i].l-1);
    }
    for(int i=1; i<=m; i++)
    {
        cout<<ans[i]<<endl;
    }
    return 0;
}

第二种方法就是经典的主席树啦(第二快)
对应的是权值线段树
对于每个区间中小于等于k的输出出现次数即可
因为数据范围比较友好不需要离散化

#include <iostream>
#include <algorithm>
using namespace std;
const int N=1e5+10;
struct node{
      int l,r,sum;
}tree[N*40];//对应的树是权值线段树
int n,m;
int root[N];//根节点
int a[N];
int tot=0;
void add(int l,int r,int pre,int &now,int p){
       tree[++tot]=tree[pre];//让新版本的线段树等于之前版本的线段树
       now=tot;
       tree[now].sum++;//开始对新版本的线段树进行修改
       if(l==r)return;
       int mid=(l+r)>>1;
       if(p<=mid)add(l,mid,tree[pre].l,tree[now].l,p);//如果p<=mid就只修改左边
       else add(mid+1,r,tree[pre].r,tree[now].r,p);//不然修改右边
}
int query(int l,int r,int L,int R,int ql,int qr){
     int mid=(l+r)>>1;
      if(ql==l&&qr==r)return tree[R].sum-tree[L].sum;//返回这段区间出现的值的次数
      if(qr<=mid)return query(l,mid,tree[L].l,tree[R].l,ql,qr);//如果询问的右界值在mid左边,就往左边去扫
      else if(ql>mid)return query(mid+1,r,tree[L].r,tree[R].r,ql,qr);//如果询问的左界值在mid右边,就直接去右边扫
      else return query(l,mid,tree[L].l,tree[R].l,ql,mid)+query(mid+1,r,tree[L].r,tree[R].r,mid+1,qr);
      //不然扫两边把sum值加起来,注意最后的ql,qr都是要变的
}
int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1;i<=n;i++){
            cin>>a[i];
            add(1,1e5,root[i-1],root[i],a[i]);
    }
    for(int i=1;i<=m;i++){
        int l,r,q;
        cin>>l>>r>>q;
        cout<<query(1,1e5,root[l-1],root[r],1,q)<<endl;
    }
    return 0;
}

第三种就是带修改莫队(最慢)
普通的莫队算法不支持修改操作
而这里因为询问的k会变
你之前的区间的值都会受影响
问了一下大佬结果学到了带修改莫队
对于普通的莫队我们再加上一个变量
t[a[x]]表示a[x]出现的次数
再新增一个变量now,在每次询问中对于now<k的情况我们就让

for(int i=now+1;i<=k;i++)now+=t[i];

对于now>k的情况进行相反操作即可
同时对于每个询问的排序要加第三关键字如果右边界处于同一块就按k大小进行排序
这里分块size大小用的是n的2/3次方
听说时间复杂度是n5/3
(但具体还没搞明白)
代码:

#include <iostream>
#include <algorithm>
#include <math.h>
using namespace std;
const int N=1e5+10;
typedef long long ll;
struct node{
     int l,r,k,i;
}q[N];
ll a[N];
int pos[N];
int t[N];
int add(int x,int num){
        t[a[x]]++;
        if(a[x]<=num)return 1;
        else return 0;
}
int del(int x,int num){
        t[a[x]]--;
        if(a[x]<=num)return 1;
        else return 0;
}
int ans[N];
int main()
{
    ios::sync_with_stdio(false);
    int n,m;
    cin>>n>>m;
    int siz=pow(n,0.66666666);
    for(int i=1;i<=n;i++){
        cin>>a[i];
        pos[i]=i/siz;
    }
    for(int i=1;i<=m;i++){
        cin>>q[i].l>>q[i].r>>q[i].k;
        q[i].i=i;
    }
    sort(q+1,q+1+m,[](node x,node y){
         if(pos[x.l]!=pos[y.l])return pos[x.l]<pos[y.l];
         else if(pos[x.r]!=pos[y.r])return pos[x.r]<pos[y.r];
         else return x.k<y.k;
         });
    int l=1,r=0;
    int cnt=0,now=0;
    for(int i=1;i<=m;i++){
        while(r<q[i].r)cnt+=add(++r,now);
        while(l>q[i].l)cnt+=add(--l,now);
        while(r>q[i].r)cnt-=del(r--,now);
        while(l<q[i].l)cnt-=del(l++,now);
        while(now<q[i].k)cnt+=t[++now];
        while(now>q[i].k)cnt-=t[now--];
        ans[q[i].i]=cnt;
    }
    for(int i=1;i<=m;i++)cout<<ans[i]<<endl;
    return 0;
}

标签:NC19427,return,int,tree,cin,换个,mid,思考,now
来源: https://blog.csdn.net/m0_53807008/article/details/117934677