JZOJ 5428. 【NOIP2017提高A组集训10.27】查询

题目

给出一个长度为n的序列a[]
给出q组询问,每组询问形如 <x,y> <script type="math/tex" id="MathJax-Element-13"> </script>,求a序列的所有区间中,数字x的出现次数与数字y的出现次数相同的区间有多少个。
数据范围
1<=n<=8000,1<=q<=500000, 1<=x,y,a[i]<=109

题解

很容易想到,每次询问的时候, O(n) 扫一遍,有一临时变量t,碰到x,t加一,碰到y,t减一,看前面有多少个t等于现在的t,按照这个计算答案。
然而这样做有个缺点:每一次都要 O(n) 扫一遍,其中有很多段t一样的,其实可以不用跑这些没有用的位置。
将x和y出现的位置找出来,排个序,然后做一遍。
总时间复杂度 O(n2)
蒟蒻有个问题,怎么计算答案。
我们知道相邻两个位置之间的t是一样的,所以只要计算t=w(w为一个数)时的t的个数,即可得到答案。

代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#define N 8010
#define P(a) putchar(a)
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
struct note{
    int to,next;
};note edge[N*10];
struct note1{
    int d,w;
};note1 qu[N*2];
int a[N],a1[N],b[N];
int o[N],head[N],q1[N],q2[N];
int ans[N][N];
int i,j,k,l,r,l1,r1,n,q,sum,wz,last;
int cnt[N*2],bz[N*2],cn,CNT,c1,c2,tot;
int read(){
    int res=0,fh=1;char ch;
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')fh=-1,ch=getchar();
    while(ch>='0'&&ch<='9')res=res*10+ch-'0',ch=getchar();
    return res*fh;
}
void write(int x){
    if(x>9)write(x/10);
    P(x%10+'0');
}
int calc(int n){return n*(n+1)/2;}
int calc1(int n){return n*(n-1)/2;}
void lb(int x,int y){edge[++tot].to=y;edge[tot].next=head[x];head[x]=tot;}
bool cmp(note1 x,note1 y){return x.d<y.d;}
int main(){
    freopen("query.in","r",stdin);
    freopen("query.out","w",stdout);
    n=read(),q=read();
    fo(i,1,n)a[i]=read(),b[i]=a[i];
    memcpy(a1,a,sizeof(a1));sort(b+1,b+n+1);cn=unique(b+1,b+n+1)-b-1;
    fo(i,1,n){
        a1[i]=lower_bound(b+1,b+cn+1,a1[i])-b;
        o[a1[i]]=a[i];
    }
    fd(i,n,1)lb(a1[i],i);
    fo(i,1,cn-1)
        fo(j,i+1,cn){
            c1=0;
            for(k=head[i];k;k=edge[k].next)
                qu[++c1].d=edge[k].to,qu[c1].w=1;
            for(k=head[j];k;k=edge[k].next)
                qu[++c1].d=edge[k].to,qu[c1].w=2;
            sort(qu+1,qu+c1+1,cmp);
            CNT++;
            bz[8000]=CNT;cnt[8000]=qu[1].d;
            qu[c1+1].d=n+1;
            wz=8000;
            ans[i][j]=calc1(qu[1].d);
            fo(k,1,c1){
                if(qu[k].w==1)wz++;else wz--;
                if(CNT!=bz[wz]){
                    bz[wz]=CNT;
                    cnt[wz]=0;
                }
                ans[i][j]+=calc1(qu[k+1].d-qu[k].d)+cnt[wz]*(qu[k+1].d-qu[k].d);
                cnt[wz]+=qu[k+1].d-qu[k].d;
            }
            ans[j][i]=ans[i][j];
        }
    fo(i,1,q){
        l=read(),r=read(); 
        l1=lower_bound(b+1,b+cn+1,l)-b;
        r1=lower_bound(b+1,b+cn+1,r)-b;
        if(o[l1]!=l)l=l1=0;if(o[r1]!=r)r=r1=0;
        if((!l&&!r) ||(l1==r1)){
            write(calc(n));P('\n');
            continue;
        }
        if((!l&&r) || (!r&&l)){
            sum=last=0;
            if(r)l1=r1;
            for(k=head[l1];k;k=edge[k].next){
                sum+=calc(edge[k].to-1-last);
                last=edge[k].to;
            }
            sum+=calc(n-last);
            write(sum);P('\n');
            continue;
        }
        write(ans[l1][r1]);
        P('\n');
    }
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值