偏序问题(2)

P1972 [SDOI2009]HH的项链 (区间不同颜色个数)

题意:给定一个序列,询问区间内不同颜色的个数
思路

  • 可以用莫队做,不过莫队复杂度是 O ( n n ) O(n\sqrt n) O(nn ) 的,1e6的数据显然过不去
  • 这里可以离线用 bit 做,以询问的右区间为界,每次出现一个重复的数的时候,就取消掉前面的,尽量更新到靠右的位置。
  • 如果是在线的话,可以用到主席树。
BIT代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e6+10;
int n,m;
int a[maxn],last[maxn],ans[maxn];
struct Opt
{
    int l,r,id;
    bool operator<(const Opt& b) const
    {
        return r<b.r;
    }
} opt[maxn];
int c[maxn];
int lowbit(int x)
{
    return x&-x;
}
void add(int x,int v)
{
    for(int i=x; i<=n; i+=lowbit(i))
        c[i]+=v;
}
int getsum(int x)
{
    int res=0;
    for(int i=x; i>0; i-=lowbit(i))
        res+=c[i];
    return res;
}
int main()
{
    scanf("%d",&n);
    for(int i=1; i<=n; ++i) scanf("%d",&a[i]);
    scanf("%d",&m);
    for(int i=1; i<=m; ++i) scanf("%d%d",&opt[i].l,&opt[i].r),opt[i].id=i;
    sort(opt+1,opt+1+m);
    int p=1;
    for(int i=1; i<=m; ++i)
    {
        while(p<=n&&p<=opt[i].r)
        {
            if(last[a[p]]) add(last[a[p]],-1);
            add(p,1);
            last[a[p]]=p;
            p++;
        }
        ans[opt[i].id]=getsum(opt[i].r)-getsum(opt[i].l-1);
    }
    for(int i=1; i<=m; ++i)
        printf("%d\n",ans[i]);
    return 0;
}
主席树 TLE 的代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e6+10;

int n,m;
int a[maxn],last[maxn];
int root[maxn],ls[maxn*40],rs[maxn*40],st[maxn*40],no;
int update(int pre,int p,int L,int R,int v)
{
	int rt=++no;
	st[rt]=st[pre]+v;
	ls[rt]=ls[pre];
	rs[rt]=rs[pre];
	if(L==R) return rt;
	int mid=(L+R)>>1;
	if(p<=mid) ls[rt]=update(ls[pre],p,L,mid,v);
	if(p>mid) rs[rt]=update(rs[pre],p,mid+1,R,v);
	return rt;
}
int query(int rt,int l,int r,int L,int R)
{
	if(!rt) return 0;
	if(l<=L&&R<=r) return st[rt];
	int mid=(L+R)>>1;
	int ans=0;
	if(l<=mid) ans+=query(ls[rt],l,r,L,mid);
	if(r>mid) ans+=query(rs[rt],l,r,mid+1,R);
	return ans;
}

int main()
{
    scanf("%d",&n);
    for(int i=1; i<=n; ++i)
	{
		scanf("%d",&a[i]);
		if(last[a[i]])
		{
			root[i]=update(root[i-1],last[a[i]],1,n,-1);
			root[i]=update(root[i],i,1,n,1);
		}
		else root[i]=update(root[i-1],i,1,n,1);
		last[a[i]]=i;
	}
    scanf("%d",&m);
	while(m--)
	{
		int l,r;
		scanf("%d%d",&l,&r);
		int ans=query(root[r],l,r,1,n);
		printf("%d\n",ans);
	}
    return 0;
}
莫队TLE的代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e6+10;

int n,m,a[maxn],ans[maxn];
int block[maxn],B;
struct Opt
{
    int l,r,aid;
    bool operator<(const Opt & b) const
    {
        if(block[l]!=block[b.l]) return l<b.l;
        return r<b.r;
    }
}opt[maxn];
int nowans,cnt[maxn];
void update(int id,int f)
{
    int p=a[id];
    cnt[p]+=f;
    if(f==1&&cnt[p]==1) nowans++;
    if(f==-1&&cnt[p]==0) nowans--;
}
int main()
{
    scanf("%d",&n);
    B=sqrt(n);
    for(int i=1;i<=n;++i)
    {
        scanf("%d",&a[i]);
        block[i]=i/B;
    }
    scanf("%d",&m);
    for(int i=1;i<=m;++i)
        scanf("%d%d",&opt[i].l,&opt[i].r),opt[i].aid=i;
    sort(opt+1,opt+1+m);
    int l=1,r=0;
    for(int i=1;i<=m;++i)
    {
        while(l<opt[i].l) update(l++,-1);
        while(l>opt[i].l) update(--l,+1);
        while(r<opt[i].r) update(++r,+1);
        while(r>opt[i].r) update(r--,-1);
        ans[opt[i].aid]=nowans;
    }
    for(int i=1;i<=m;++i)
        printf("%d\n",ans[i]);
    return 0;
}

H. Interval 2020暑期牛客第五场

在这里插入图片描述
题意:求区间 [ l , r ] [l,r] [l,r] 内所有子区间连续 & 的不同值的个数
思路:一个数最多 30 位连续取 &最多变化30次,我们把所有的 [ l , r ] [l,r] [l,r] 的值都算出来,处理出三元组 ( l , r , k ) (l,r,k) (lrk)。最多有 30n 个三元组。然后就类似统计区间不同的颜色的个数,这里强制在线,那么就更新到主席树上。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+10;
 
int n,q;
int a[maxn],last[30];
int root[maxn*30],ls[maxn*30*40],rs[maxn*30*40],st[maxn*30*40],no;
vector<int> vec;
vector<pair<int,int> > opt[maxn];
unordered_map<int,int> pre;
 
int update(int pre,int p,int L,int R,int v)
{
    int rt=++no;
    st[rt]=st[pre]+v;
    ls[rt]=ls[pre];
    rs[rt]=rs[pre];
    if(L==R) return rt;
    int mid=(L+R)>>1;
    if(p<=mid) ls[rt]=update(ls[pre],p,L,mid,v);
    if(p>mid) rs[rt]=update(rs[pre],p,mid+1,R,v);
    return rt;
}
int query(int rt,int l,int r,int L,int R)
{
    if(!rt) return 0;
    if(l<=L&&R<=r) return st[rt];
    int mid=(L+R)>>1;
    int ans=0;
    if(l<=mid) ans+=query(ls[rt],l,r,L,mid);
    if(r>mid) ans+=query(rs[rt],l,r,mid+1,R);
    return ans;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;++i) scanf("%d",&a[i]);
    for(int i=1;i<=n;++i)
    {
        vec.clear();
        for(int j=0;j<30;++j)
            if(a[i]>>j&1) vec.push_back(last[j]);
            else last[j]=i;
        sort(vec.begin(),vec.end(),greater<int>());
        vec.resize(unique(vec.begin(),vec.end())-vec.begin());
        opt[i].push_back({i,a[i]});
        int cur=a[i];
        for(auto j: vec)
        {
            if(!j) break;
            cur&=a[j];
            opt[i].push_back({j,cur});
        }
    }
    for(int i=1;i<=n;++i)
    {
        int rt=root[i-1];
        for(auto x: opt[i])
        {
            int l=x.first,v=x.second;
            if(!pre[v]) root[i]=update(rt,l,1,n,1);
            else if(pre[v]<l)
            {
                root[i]=update(rt,pre[v],1,n,-1);
                root[i]=update(root[i],l,1,n,1);
            }
            pre[v]=l;
            rt=root[i];
        }
    }
    scanf("%d",&q);
    int ans=0;
    while(q--)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        l=(l^ans)%n+1,r=(r^ans)%n+1;
        if(l>r) swap(l,r);
        ans=query(root[r],l,r,1,n);
        printf("%d\n",ans);
    }
    return 0;
}

这里与上面的区间是把一个值都更新到每一个包含它的区间上。比如, l = 2 , r = 3 , k = 2 l=2,r=3,k=2 l=2r=3k=2。那么这个 k k k 包含在 [ 1 , 3 ] 、 [ 2 , 3 ] [1,3]、[2,3] [1,3][2,3] 两个区间上。所以做区间更新 [ 1 , 2 ] [1,2] [1,2] ,然后查询的时候,查询 [ l , r ] [l,r] [l,r] 只需要在 r o o t [ r ] root[r] root[r] 这棵树上单点查询 l l l 这个点即可。更新的时候需要注意不能重复更新。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+10;
 
int n,q;
int a[maxn],last[30];
int root[maxn*30],ls[maxn*30*40],rs[maxn*30*40],st[maxn*30*40],no;
vector<int> vec;
unordered_map<int,int> maxl;
 
int update(int pre,int l,int r,int L,int R)
{
    int rt=++no;
    ls[rt]=ls[pre];
    rs[rt]=rs[pre];
    st[rt]=st[pre];
    if(l<=L&&R<=r)
    {
        st[rt]++;
        return rt;
    }
    int mid=(L+R)>>1;
    if(l<=mid) ls[rt]=update(ls[pre],l,r,L,mid);
    if(r>mid) rs[rt]=update(rs[pre],l,r,mid+1,R);
    return rt;
}
int query(int rt,int p,int L,int R)
{
    if(!rt) return 0;
    if(L==R) return st[rt];
    int mid=(L+R)>>1;
    if(p<=mid) return st[rt]+query(ls[rt],p,L,mid);
    if(p>mid) return st[rt]+query(rs[rt],p,mid+1,R);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;++i) scanf("%d",&a[i]);
    for(int i=1;i<=n;++i)
    {
        vec.clear();
        for(int j=0;j<30;++j)
            if(a[i]>>j&1) vec.push_back(last[j]);
            else last[j]=i;
        sort(vec.begin(),vec.end(),greater<int>());
        vec.resize(unique(vec.begin(),vec.end())-vec.begin());
        int cur=a[i];
        int l=maxl[cur]+1,r=i;
        if(l<=r)
        {
            root[i]=update(root[i-1],l,r,1,n);
            maxl[cur]=i;
        }
        for(auto j: vec)
        {
            if(!j) break;
            cur&=a[j];
            l=maxl[cur]+1,r=j;
            if(l<=r)
            {
                root[i]=update(root[i],l,r,1,n);
                maxl[cur]=j;
            }
        }
    }
    scanf("%d",&q);
    int ans=0;
    while(q--)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        l=(l^ans)%n+1,r=(r^ans)%n+1;
        if(l>r) swap(l,r);
        ans=query(root[r],l,1,n);
        printf("%d\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值