【CQOI2008】中位数
题不难,但是思路有意思,这个是我自己想出来的OvO
原题:
给出1~n的一个排列,统计该排列有多少个长度为奇数的连续子序列的中位数是b。中位数是指把所有元素从小到大排列后,位于中间的数。
n<=100000
刚看这道题的时候看n滋瓷nlogn的复杂度,又是维护区间关系的,觉得应该要用数据结构
然后开始想数据结构,思考中位数的特殊性,题目中限定子序列的长度为奇数,b是一个长度为奇数的序列的中位数就说明在这个序列中比b大的数和比b小的数相等
子序列中的东西有多少个,这个可以用前缀搞
然后发现让前缀中比b大的数和比b小的数做差,如果两个前缀和相等的话,说明这个这两个前缀和中间的区间比b大的数和比b小的数相等
证明的话可以设前缀和a,b比b大和比b小的数分别为(x1,y1),(x2,y2),差的前缀和为y1-x1,y2-x2,这两个前缀和中间比b大的数有x2-x1,比b小的有y2-y1,如果两个差的前缀和相等,则y1-x2=y2-x2,移项就可以得到x2-x1=y2-y1,满足子序列中比b大的数个数和比b小的数个数相等,即这个子序列的中位数就是b
子序列长度为奇数的条件很好满足,因为我们是两个前缀和相减,所以可以把前缀和分成两份,分别是右顶点为奇数和偶数的前缀和,让两个奇偶性不同的前缀和相减就可以了
具体怎么搞的话,就是搞一个cnt记录直到第i个数比b大的数个数减比b小的数个数是多少,每次看和i奇偶性相反,值为cnt的前缀和个数有多少个,加到答案上即可
不用记录前缀和,直接搞一个计数,s[i][j]表示值为i,奇偶性为j的前缀和有多少个就可以辣
两个前缀和的值相等但是这两个前缀和中间的子序列中没有出现b的情况是不存在的,因为子序列一定是奇数,没有b出现的话,一个数要么比b大,要么比b小,奇数个数没法平分
代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 #include<cmath> 6 #include<queue> 7 using namespace std; 8 int read(){int z=0,mark=1; char ch=getchar(); 9 while(ch<'0'||ch>'9'){if(ch=='-')mark=-1; ch=getchar();} 10 while(ch>='0'&&ch<='9'){z=(z<<3)+(z<<1)+ch-'0'; ch=getchar();} 11 return z*mark; 12 } 13 int n,m,s[210000][2]; 14 int main(){//freopen("ddd.in","r",stdin); 15 cin>>n>>m; 16 int u,cnt=0,ans=0; 17 s[n][0]=1; 18 for(int i=1;i<=n;++i){ 19 u=read(); 20 if(u!=m) cnt+=u>m?1:-1; 21 ans+=s[cnt+n][i&1^1];//先与1判断奇偶,然后再^1取到和i奇偶性相反的奇偶性 22 s[cnt+n][i&1]++; 23 } 24 cout<<ans<<endl; 25 return 0; 26 }