kd-tree講解 & bzoj 2648 & 2716 & 3053 題解

【KD-TREE介紹】在SYC1999大神的“蠱惑”下,我開始接觸這種算法。

首先,大概的概念可以去百度百科。具體實現,我是看RZZ的代碼長大的。

我們可以想象在平面上有N個點。首先,按橫座標排序找到最中間的那個點。然後水平劃一條線,把平面分成左右兩個部分。再遞歸調用左右兩塊。注意,在第二次(偶數次)調用的時候,是找到縱座標中最中間的點,並垂直畫一條線。

這樣效率看上去很好。維護的時候有點像線段樹。每個點記錄它的座標、它轄管的區間4個方向的極值、它的左右(或上下)的兩個點的標號。遞歸兩個子樹時,注意要up更新這個點轄管的範圍。

inline int cmp(arr a,arr b){return a.d[D]<b.d[D]||a.d[D]==b.d[D]&&a.d[D^1]<b.d[D^1];}
inline void up(int k,int s)
{
  a[k].min[0]=min(a[k].min[0],a[s].min[0]);
  a[k].max[0]=max(a[k].max[0],a[s].max[0]);
  a[k].min[1]=min(a[k].min[1],a[s].min[1]);
  a[k].max[1]=max(a[k].max[1],a[s].max[1]);
}
int build(int l,int r,int dd)
{
  D=dd;int mid=(l+r)>>1;
  nth_element(a+l+1,a+mid+1,a+r+1,cmp);
  a[mid].min[0]=a[mid].max[0]=a[mid].d[0];
  a[mid].min[1]=a[mid].max[1]=a[mid].d[1];
  if (l!=mid) a[mid].l=build(l,mid-1,dd^1);
  if (mid!=r) a[mid].r=build(mid+1,r,dd^1);
  if (a[mid].l) up(mid,a[mid].l);
  if (a[mid].r) up(mid,a[mid].r);
  return mid;
}

介紹一下nth_element這個STL。頭文件就是algorithm。它相當於快排的一部分,調用格式如上。意思是把第MID個數按cmp放在中間,把比mid“小”的數放在左邊,否則放在右邊。(注意:不保證左邊和右邊有序)

上述代碼很好理解。

然後先在我要支持加入點,也是類似於線段樹的思想:

void insert(int k)
{
  int p=root;D=0;
  while (orzSYC)
  {
    up(p,k);
    if (a[k].d[D]<=a[p].d[D]){if (!a[p].l) {a[p].l=k;return;} p=a[p].l;}
    else {if (!a[p].r) {a[p].r=k;return;} p=a[p].r;}
    D^=1;
  }
}

爲什麼我忽然覺得是splay的insert操作?就是每次往某個點的左或右(或者上或下)過去。

比如我們要查詢與(x,y)最近的點(曼哈頓距離)與其的距離。

int getdis(int k)
{
  int res=0;
  if (x<a[k].min[0]) res+=a[k].min[0]-x;
  if (x>a[k].max[0]) res+=x-a[k].max[0];
  if (y<a[k].min[1]) res+=a[k].min[1]-y;
  if (y>a[k].max[1]) res+=y-a[k].max[1];
  return res;
}
void ask(int k)
{
  int d0=abs(a[k].d[0]-x)+abs(a[k].d[1]-y);
  if (d0<ans) ans=d0;
  int dl=(a[k].l)?getdis(a[k].l):INF;
  int dr=(a[k].r)?getdis(a[k].r):INF;
  if (dl<dr){if (dl<ans) ask(a[k].l);if (dr<ans) ask(a[k].r);}
  else {if (dr<ans) ask(a[k].r);if (dl<ans) ask(a[k].l);}
}

getdis有點像Astar中的“估價函數”。計算(x,y)與當前點範圍的差距有多少,然後按順序遍歷左二子和右兒子。這樣,如果更新到最優值,就能及時退出。這種算法在隨機數據上是lg的,但是在構造數據上約是sqrt的。

【BZOJ2716&2648】雙倍經驗。就是裸的K-D TREE模板套套。無壓力1A~。

【BZOJ3053】哎,說多了都是淚。這道題調了不知道多少時間。首先,它拓展到了K維空間上。這樣,cmp就只需判斷某一位的大小就行了。然後要查詢前m優值。因爲m<=10,我爲了效率,直接一遍做,開了一個數組記錄最優值。然後判斷最優值的時候裸O(n)(均攤)的更新答案。

對於那個估計函數也要稍稍改一下(因爲是歐幾里得距離),怎麼方便怎麼來!(反正只會影響到效率)

調了半天后,總算小數據對拍沒有問題了~~浪交!T了。。。

後來我估計在更新答案時速度太慢,於是一咬牙,把10個最優解開成了隊列......

然後大數據對拍~~什麼,秒WA?這下真的調了一個下午(因爲我是剛學的),後來發現:RZZ的博客裏的nth過程用錯了。比如從l到r,中間是mid(默認數組下標從1開始),應該是a+l,a+mid,a+r+1。最後一個+1因爲是虛指針。但是前面都不用+1的(上面的代碼已經修改過了)!!!!

最後又是RE。果斷要數據!——發現只有一個測試點。我先寫了個程序,把測試點拆成了好幾個。然後一測:全過!和在一起:RE!原來,l和r要及時清零!!!呵呵,多麼痛的領悟!

【截取程序】

var
  ss:string;
  cnt,n,m,a,i,j:longint;
begin
  assign(input,'T.in');
  reset(input);
  while (not(eof)) do
    begin
      inc(cnt);
      str(cnt,ss);
      ss:='T'+ss+'.in';
      assign(output,ss);
      rewrite(output);
      readln(n,m);
      writeln(n,' ',m);
      for i:=1 to n do
        begin
          for  j:=1 to m do
            begin
              read(a);
              write(a,' ');
            end;
          writeln;
        end;
      readln(n);
      writeln(n);
      for i:=1 to n do
        begin
          for j:=1 to m do
            begin
              read(a);
              write(a,' ');
            end;
          writeln;
          read(a);
          writeln(a);
        end;
      close(output);
    end;
end.

【對拍造數據】

#include<cstdio>
#include<cstdlib>
#include<ctime>
using namespace std;
int main()
{
  freopen("T.in","w",stdout);
  srand((int)time(0));
  int n=50000,m=4,i,j;
  printf("%d %d\n",n,m);
  for (i=1;i<=n;i++)
  {
    for (j=1;j<=m;j++)
      printf("%d ",rand()%10000+1);
    printf("\n");
  }
  int Q=1000;
  printf("%d\n",Q);
  while (Q--)
  {
    for (i=1;i<=m;i++)
      printf("%d ",rand()%10000+1);
    printf("%d\n",rand()%5+1);
  }
  return 0;
}

【AC代碼】

#include<cstdio>
#include<algorithm>
#include<queue>
#define N 50005
#define INF 21390627567143.0
using namespace std;
const int orzSYC=1;
struct arr
{
  int d[5],max[15],min[15],l,r,id;
  arr() {l=0;r=0;id=0;}
}a[N*4],aa[N];
struct pop
{
  double x;int id;
  friend bool operator < (const pop &a,const pop &b){return a.x<b.x;}
};
priority_queue<pop>ans;
int n,m,Q,i,j,t,x[15],D,temp[21],root,opt,P,flag;
inline int Read()
{
  int x=0;char ch=getchar();bool positive=1;
  for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') positive=0;
  for (;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
  return positive?x:-x;
}
inline int cmp(arr a,arr b)
{
  return a.d[D]<b.d[D];
}
inline void up(int k,int s)
{
  for (int i=0;i<m;i++)
  {
    a[k].min[i]=min(a[k].min[i],a[s].min[i]);
    a[k].max[i]=max(a[k].max[i],a[s].max[i]);
  }
}
int build(int l,int r,int dd)
{
  D=dd;int mid=((l+r)>>1);
  nth_element(aa+l,aa+mid,aa+r+1,cmp);
  for (int i=0;i<m;i++)
    a[mid].min[i]=a[mid].max[i]=a[mid].d[i]=aa[mid].d[i];
  a[mid].id=mid;
  if (l<mid) a[mid].l=build(l,mid-1,(dd+1)%m);else a[mid].l=0;
  if (mid<r) a[mid].r=build(mid+1,r,(dd+1)%m);else a[mid].r=0;
  if (a[mid].l) up(mid,a[mid].l);
  if (a[mid].r) up(mid,a[mid].r);
  return mid;
}
inline double sdis(int k)
{
  double res=0;
  for (i=0;i<m;i++)
  {
    res+=(a[k].d[i]-x[i])*(a[k].d[i]-x[i]);
  }
  return res;
}
void ask(int k,int deep)
{
  int L=a[k].l,R=a[k].r;
  if (x[deep]>=a[k].d[deep]) swap(L,R);
  double now=sdis(k);
  if (L) ask(L,(deep+1)%m);
  int flag=0;
  if (ans.size()<P) {ans.push((pop){now,k});flag=1;}
  else
  {
    if (now<ans.top().x) ans.pop(),ans.push((pop){now,k});
    if ((x[deep]-a[k].d[deep])*1.*(x[deep]-a[k].d[deep])<ans.top().x) flag=1;
  }
  if (flag&&R) ask(R,(deep+1)%m);
}
int main()
{
  while (scanf("%d%d",&n,&m)!=EOF)
  {
    for (i=1;i<=n;i++) 
      for (j=0;j<m;j++)
        aa[i].d[j]=Read();
    root=build(1,n,0);
    Q=Read();
    while (Q--)
    {
      for (i=0;i<m;i++) x[i]=Read();
      P=Read();
      ask(root,0);int wri=0;
      printf("the closest %d points are:\n",P);
      while (!ans.empty())
      {
        temp[++wri]=ans.top().id;
        ans.pop();
      }
      for (i=wri;i;i--)
      {
        for (j=0;j<m-1;j++)
          printf("%d ",a[temp[i]].d[j]);
        printf("%d\n",a[temp[i]].d[m-1]);
      }
    }
  }
  return 0;


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章