可以先做做3874 哪道题数据小 不用离散化
题意是让询问区间和 出现过多次的只能算一次 很明显的线段树
先对询问区间按右值排序(也可以按左值 其实都一样)并记录输入的顺序(用来按输入的顺序输出) 然后进行跟新和查询 如下
对每一个要跟新的值先判断之前出没出现过 如果出现过就消除之前的 再跟新当前的 并记录位置(此处就是为什么要用到离散化) 如果当前跟新的和查询的右值相同则查询
这样做的原因是消除之前出现过的是不会对当前造成影响(想想为什么) 还有就是跟新比当前右值大的位置就没有意义 具体看代码 还有就是注意__int64
#include<stdio.h> #include<string.h> #include<algorithm> #include<iostream> using namespace std; #define LL(x) (x<<1) #define RR(x) ((x<<1)|1) struct node { int L,R,ii; }A[100010]; struct Node { int i; int tt; }B[30010]; int map[30010]; int cmp(node a,node b) { return a.R<b.R; } int cmp1(Node a,Node b) { return a.tt<b.tt; } int cmp2(Node a,Node b) { return a.i<b.i; } __int64 num[4*30010]; int update(int L,int R,int pos,int mark,int k) { if(k>0)num[mark]+=map[k]; else num[mark]-=map[-k]; if(L==R&&L==pos) { return 0; } int mid=(L+R)/2; if(pos<=mid) { update(L,mid,pos,LL(mark),k); } else update(mid+1,R,pos,RR(mark),k); return 0; } __int64 find(int L,int R,int left,int right,int mark) { if(L==left&&R==right) { return num[mark]; } int mid=(L+R)/2; if(right<=mid) { return find(L,mid,left,right,LL(mark)); } else if(left>mid) { return find(mid+1,R,left,right,RR(mark)); } else return find(L,mid,left,mid,LL(mark))+find(mid+1,R,mid+1,right,RR(mark)); } int main() { int T,i,j,n,m,a,b; int leap[30010]; __int64 print[100010]; scanf("%d",&T); while(T--) { scanf("%d",&n); for(i=1;i<=n;i++) { scanf("%d",&B[i].tt); B[i].i=i; } scanf("%d",&m); for(i=1;i<=m;i++) { scanf("%d%d",&A[i].L,&A[i].R); A[i].ii=i; } memset(num,0,sizeof(num)); sort(B+1,B+1+n,cmp1); sort(A+1,A+1+m,cmp); int k=-1; j=0; for(i=1;i<=n;i++) { if(B[i].tt!=k) { k=B[i].tt; j++; map[j]=k; B[i].tt=j; } else B[i].tt=j; } sort(B+1,B+1+n,cmp2); memset(leap,0,sizeof(leap)); for(i=1,j=1;i<=n&&j<=m;i++) { if(leap[B[i].tt]) update(1,n,leap[B[i].tt],1,-B[i].tt); update(1,n,i,1,B[i].tt); leap[B[i].tt]=i; while(i==A[j].R) { print[A[j].ii]=find(1,n,A[j].L,A[j].R,1); j++; if(j>m) break; } } for(i=1;i<=m;i++) printf("%I64d\n",print[i]); } return 0; }
时间: 2024-11-06 09:42:49