2018.06.29 NOIP模拟 区间(前缀和差量)

区间(interval.cpp)

时限:2000ms

空间限制:512MB

【问题描述】
给出一个长度为 n 的序列 a[1]…a[n]。
给出 q 组询问,每组询问形如&lt;x,y&gt;&lt;x,y&gt;<x,y>,求 a 序列的所有区间中,数字 x 的出现次数与数
字 y 的出现次数相同的区间有多少个。
【输入格式】
第一行两个数 n 和 q。
第二行 n 个数 a[i]。
接下来 q 行,每行两个数 x,y 表示一组询问。
【输出格式】
输出 q 行,每行一个数表示对应询问的答案。
【样例输入】
3 2
1 2 1
1 2
4 5
【样例输出】
2
6
【数据规模与约定】
对于 30%的数据:1≤n≤100;1≤q≤1000。
对于另外 30%的数据:序列中只有最多 50 种不同数字且 1≤n≤1000。
对于 100%的数据:1≤n≤8000;1≤q≤500000;1≤x,y,a[i]≤10^9。


这题数据有点吓人,考试时搞了一个多小时发现读错了题就知道自己没救了,匆忙之中写了60分暴力,结果303030分直接RERERE,还有303030TLETLETLE了,弄得我不知道怎么是好,下来听了听题解,发现其实并没有我想的那么复杂。

那么我们首先要算算效率,如果我们跑个均摊O(n2)O(n^2)On2的算法似乎刚好卡住2s2s2s时限,于是再仔细思考一下,如果我们维护一个xxxyyy的区间的出现次数的前缀和,显然如果两个前缀和的值相同的话,这两个前缀和之间形成的区间肯定xxxyyy出现的次数是相同的,这样可以修改答案。但我们再细算一下,对于每次询问,我们要关心的只是xxxyyy的位置,而其他位置的前缀和显然可以对之前的前缀和进行继承。所以这个地方一个小技巧,我们先将aaa数组离散化,然后用动态数组vectorvectorvector来记录每种数的下标。直接在动态数组中跳下标统计答案就行了。其他细节详见代码。

代码如下

#include<bits/stdc++.h>

//常量 
#define N 8005
#define inf 1000000000

using namespace std;

//读入优化 
inline long long read(){
	long long ans=0;
	char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))ans=(ans<<3)+(ans<<1)+ch-'0',ch=getchar();
	return ans;
}

//变量 
int n,sum[N<<1],b[N],a[N],siz,q;
vector<int>pos[N],recycle;
map<int,int>s;

//计算区间个数
inline int cal(int x){return x==0?0:x*(x-1)/2;}

int main(){
	n=read(),q=read();
	for(int i=1;i<=n;++i)b[i]=a[i]=read();

//离散化 
	sort(b+1,b+n+1);
	siz=unique(b+1,b+n+1)-b-1;
	for(int i=1;i<=siz;++i)s[b[i]]=i;
	for(int i=1;i<=n;++i)pos[s[a[i]]].push_back(i);

//防止越界	
	for(int i=0;i<=siz;++i)pos[i].push_back(n+1);
	
//询问
	while(q--){
		int x=s[read()],y=s[read()];
		int head1=0,head2=0,last=0,delta=0,ans=0;
		++sum[n];
		while(last!=n){
			if(pos[x][head1]<pos[y][head2]){
				int len=pos[x][head1]-last-1;
				ans+=len*sum[n+delta]+cal(len);
				sum[n+delta]+=len;
				last=pos[x][head1++]-1,recycle.push_back(n+delta),++delta;
			}
			else{
				int len=pos[y][head2]-last-1;
				ans+=len*sum[delta+n]+cal(len);
				sum[delta+n]+=len;
				last=pos[y][head2++]-1,recycle.push_back(n+delta),--delta;
			}
		}
		while(!recycle.empty())sum[recycle.back()]=0,recycle.pop_back();
		cout<<ans<<'\n';
	}
	return 0;
}

转载于:https://www.cnblogs.com/ldxcaicai/p/9738558.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值