HDU 6621 K-th Closest Distance
Time limit 15000 ms
Memory limit 524288 kB
OS Windows
中文题意
给一个序列,每次询问一个区间,问该区间内的所有数字,和p的差值的绝对值,组成的新序列里,第k大的是多少。换句话说,把该区间里的数字和每次询问给的数字p画到数轴上,问距离p点第k近的点,到p的距离。
解题思路
这种要把数字画到数轴上的,显然就是权值线段树,还要询问不同的子区间,那就是主席树了。使用主席树我们可以较快地得到一个给定区间内的数字在数轴上的情况。
观察到每次的k很小,最大169(真奇怪),时限15s,感觉也挺多。于是我们比赛时用的方法是,现在数轴上把p的排名找到,假设p在这个区间能排到第k名,那么我们就在数轴上往两边扩展,向左找出第k-1名、第k-2名、第k-3名……一直找到边上或者找够k个,在右边同理,找到最多k个数字,那么最多就有2k个数字,也就三百多。把它们按照到p的距离升序排序,输出第k个就好。然后就交,然后TLE,然后卡常再交,再TLE。比赛结束,我们这题是红色的-10……比赛最后半小时,因为这题很多人多次TLE(像我),评测姬卡了几十页,起到了封榜的作用(雾)。
正解如下——我们每次二分答案,二分的上下限是\([0,sz]\),sz是序列中最大的数字。每次查询该区间内大小在\([p-mid,p+mid]\)范围内的数字个数,它们到达p的距离均小于等于\(mid\),如果多于k,说明范围要更小,如果少于k,说明范围要更大,如果等于k,也说明范围要缩小,因为我们要找到精确的范围。就这样二分,得到答案……这样子AC只需要4s。
另外,这题的数字范围也就\(10^6\),用不着离散化。离散化反而增加难度且浪费时间
源代码
那大片注释就是按照比赛过程中那个思路写的不知道啥玩意
#include<stdio.h>
#include<algorithm>
const int N=100010;
int T;
int n,m;
int a[N];
void read(int &x)
{
int f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
x*=f;
}
int sz;
struct Node{
int ls,rs;
int sum;
}t[N*55];
int root[N],cnt=1;
int anss[2000],num;
void update(int &x,int pre,int l,int r,int pos)//pos位置加一
{
t[x=cnt++]=t[pre];
t[x].sum++;
if(l==r) return;
int mid=l+r>>1;
if(pos<=mid) update(t[x].ls,t[pre].ls,l,mid,pos);
else update(t[x].rs,t[pre].rs,mid+1,r,pos);
}
int quek(int srt, int trt, int l, int r, int k)//第k大
{
if (l == r)
return l;
int mid = l + r >> 1;
int delta = t[t[trt].ls].sum - t[t[srt].ls].sum;
if (k <= delta)
return quek(t[srt].ls, t[trt].ls, l, mid, k);
else
return quek(t[srt].rs, t[trt].rs, mid + 1, r, k - delta);
}
int quesum(int x,int pre,int l,int r,int ql,int qr)//从1到p的和
{
if(ql<=l&&r<=qr) return t[x].sum-t[pre].sum;
int mid=l+r>>1;
int ans=0;
if(ql<=mid) ans+=quesum(t[x].ls,t[pre].ls,l,mid,ql,qr);
if(qr>mid) ans+=quesum(t[x].rs,t[pre].rs,mid+1,r,ql,qr);
return ans;
}
int main()
{
//freopen("test.in","r",stdin);
read(T);
while (T--)
{
int lastans = 0;
cnt = 1;
read(n);read(m);
sz=0;
for (int i = 1; i <= n; i++)
{
read(a[i]);
sz=std::max(sz,a[i]);
}
root[0]=0;t[0]={0,0,0};
for (int i = 1; i <= n; i++)
{
update(root[i], root[i - 1], 1, sz, a[i]);
}
for (int i = 1,ll,rr,p,k;i<=m;i++)
{
read(ll);
read(rr);
read(p);
read(k);
//scanf("%d%d%d%d",&ll,&rr,&p,&k);
ll^=lastans;rr^=lastans;p^=lastans;k^=lastans;
//printf("%d %d %d %d ************\n",ll,rr,p,k);
// int p_rk=quesum(root[rr],root[ll-1],1,sz,1,id(p));//问p是区间第几大
//printf("%d^&\n",p_rk);
//p_rk向两边扩展,知道扩展了k个,排序,取第k个
//int left=p_rk,right=p_rk+1;
/*num=1;
while(num<=k&&right<=rr-ll+1)
{
anss[num++]=std::abs(val[quek(root[ll-1],root[rr],1,sz,right)]-p);
right++;
}
while(num<=(k<<1)&&left>=1)
{
anss[num++]=std::abs(val[quek(root[ll-1],root[rr],1,sz,left)]-p);
left--;
}
std::sort(anss+1,anss+num);
//for(int i=1;i<num;i++) printf("%d ",anss[i]);
//puts("");
lastans=anss[k];*/
int pl = 0, pr = sz;
//p=p_rk;
while(pl <= pr) {
int mid = (pl + pr) >> 1;
if (quesum(root[rr], root[ll - 1], 1, sz, std::max(1, p - mid), std::min(sz, p + mid) ) >= k)
{
lastans = mid;
pr = mid - 1;
} else pl = mid + 1;
}
printf("%d\n",lastans);
}
}
return 0;
}