2223: [Coci 2009]PATULJCI
Time Limit: 10 Sec Memory Limit: 259 MBSubmit: 1414 Solved: 612
[Submit][Status][Discuss]
Description
Input
10 3 1 2 1 2 1 2 3 2 3 3 8 1 2 1 3 1 4 1 5 2 5 2 6 6 9 7 10
Output
no
yes 1
no
yes 1
no
yes 2
no
yes 3
HINT
Notice:输入第二个整数是序列中权值的范围Lim,即1<=ai(1<=i<=n)<=Lim。
1<=Lim<=10000
分析:
其实这道题如果是第一次做还是有点难想出来的.它的突破口是什么呢?我们可以先查询权值在区间[L,R]内的数的出现次数是否大于(r - l + 1) / 2,是的话查询左半区间和右半区间是否分别大于.只要一个区间大于,就可以往那个子区间递归,这样不断地缩小区间大小,直到区间的L == R,就能确定那个数了.这就类似于二分找第k小的数的过程,每次判断小于当前数的数有多少个,最后不断缩小范围,或许这是求解已知排名的数是多少的通用解法吧.
主席树的应用.
插入操作就是普通的插入,查询操作采用步步逼近的方法,如果左区间的数的个数大于(r - l + 1) / 2,就在左区间中找,否则在右区间中找.最后直到l == r,返回l就可以了.
应用主席树解题主要是要想到它的性质.是权值线段树,记录前缀等等.
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int maxm = 500005; int n,q,maxn,cnt,root[maxm]; struct node { int left,right,sum; }e[maxm * 20]; void update(int x,int &y,int l,int r,int v) { e[y = ++cnt] = e[x]; e[y].sum++; if (l == r) return; int mid = (l + r) >> 1; if (v <= mid) update(e[x].left,e[y].left,l,mid,v); else update(e[x].right,e[y].right,mid + 1,r,v); } int query(int l,int r,int x,int y,int k) { if (l == r) return l; int mid = (l + r) >> 1; if (e[e[y].left].sum - e[e[x].left].sum > k) return query(l,mid,e[x].left,e[y].left,k); if (e[e[y].right].sum - e[e[x].right].sum > k) return query(mid + 1,r,e[x].right,e[y].right,k); return 0; } int main() { scanf("%d%d",&n,&maxn); for (int i = 1; i <= n; i++) { int x; scanf("%d",&x); update(root[i - 1],root[i],1,maxn,x); } scanf("%d",&q); while (q--) { int l,r; scanf("%d%d",&l,&r); int ans = query(1,maxn,root[l - 1],root[r],(r - l + 1) / 2); if (ans == 0) puts("no"); else printf("yes %d ",ans); } return 0; }