HDOJ1156 线段树

这道题做的确实太过于艰难,网上的解答基本都写得很简略,直接就把解答代码贴上去了,综合看了很多解答,在加上了一些自己的思考,总算把这道题攻克了。

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1156

题目大意是:输入n以及n个点的坐标,Stan任意选一个点做竖线,随后Ollie在被Stan的竖线穿过的点中,选一个点做横线,两个人的线将平面分成四个象限。其中第1、3象限内的点的数量为Stan的得分,第2、4象限内点的数量为Ollie的得分。Stan所有可能做的竖线中,每种竖线情况下,Stan最少得分的最大值,以及在Stan得到最少得分的最大值的时候,Ollie对应的得分。Stan和Ollie都尽可能让自己的得分更大,由于Ollie在Stan之后做横线,当Stan做完竖线后,Ollie做横线一定会让自己的得分尽可能大。

例如:Stan可以做三条竖线x=1,x=2,x=3,做x=1竖线Stan的最少得分为3,做x=2竖线Stan的最少得分为7,做x=3竖线Stan的最少得分为5,则Stan最少得分的最大值为7,同时输出Stan得7分的时候Ollie可能的得分。

整个题目的求解过程以样例给的输入为例:有11个点(3,2),(3,3),(3,4),(3,6),(2,-2),(1,-3),(0,0),(-3,-3),(-3,-2),(-3,-4),(3,-7)。求Stan最少得分的最大值以及Ollie对应的可能的得分。

 

首先我们给平面内的所有点分配一个独一无二的id,定义五个变量:left,right,up,down,large。其中left[id]表示在点id左边与id纵坐标相同的点的个数,right[id]表示在点id右边与id纵坐标相同的点的个数,up[id]表示在点id上面与id横坐标相同的点的个数,down[id]表示在点id下方横坐标与id相同的点的个数,large[id]表示纵坐标比点id大的个数。

此时产生问题一:这五个变量怎么求解?首先将这五个数组初始化为0。将所有点按照纵坐标优先排序,如果纵坐标相同再按照横坐标从小到大排序。排好序以后,从第一个点到最后一个点遍历,遍历到第i个点的时候,如果第i个点的纵坐标与第i-1个点的纵坐标相同,则left[第i个点]=left[第i-1个点]+1。从最后一个点到第一个点遍历,同理可以求解right。从最后一个点到第一个点遍历同样可以求解large,定义一个变量same,表示与上一个点纵坐标相同的点的个数。若第i个点纵坐标与第i-1个点纵坐标不同,则large[第i个点]=large[第i-1个点]+same,并将same重新设置成1;若第i个点纵坐标与第i-1个点的纵坐标相同,则large[第i个点]=large[第i-1个点],并且将same增加1。求解up和down同理,只是将所有点按照横坐标优先排序。

 

好的现在第一个问题解决了,下面是具体的求解过程,我们设BL、TL、TR、BR分别为某个点左下、左上、右上、右下点的个数(不包括横坐标或纵坐标相同的点)。

整个求解过程为(以下left,up,right,down,large都是遍历的那个点的left,up,right,down,large):

按照横坐标优先排序。

i=1,遍历(-3,-4)点,查询遍历过的点中,纵坐标小于等于-4的点个数sum,BL=sum-left-down,TL=i-1-BL-left-down(由于之前已经遍历了i-1个点,并且这些点一定是在当前遍历点的左方或和当前点横坐标相同的下方),TR=large-up-TL,BR=n-1-BL-TL-TR-left-up-right-down。计算如果以(-3,-4)为原点划分象限,Stan和Ollie的得分。

i=2,同理,遍历到(-3,-3)点。

现在产生第二个问题:查询操作怎么完成?用线段树来维护。例如:遍历到(0,0)点的时候,通过线段树查询遍历过的点纵坐标<0的点的个数。最小的纵坐标为-4,即查询遍历过的点中纵坐标在[-4,0]范围内的点即可。计算完Stan和Ollie的得分后,再把(0,0)点插入到线段树当中。

此时还有一个小问题,就是纵坐标的范围太大了,需要将纵坐标离散化处理。以上11个点中纵坐标最小的点为(3,-7)所以该点离散化纵坐标为1,纵坐标第二小的点(-3,-4)离散化纵坐标为2,(-3,-3)和(1,-3)离散化纵坐标为3以此类推。目前我还真不知道这种操作为什么叫做离散化。实现它只要按照纵坐标优先排序,从第一个点到最后一个点遍历即可,纵坐标相同的点离散化纵坐标相同。

解题代码:

#include<iostream>
#include<cstdlib>
#include<climits>
#include<set>
#include<vector>
using namespace std;

int n;
const int maxn=200010;
struct Point{
  int x;
  int y;
  int id;
};
Point p[maxn];//坐标
int _left[maxn],_right[maxn],_down[maxn],_up[maxn],_large[maxn];//下标:点的id
int discritizedY[maxn];//下标:点的id,离散化纵坐标
Point tmp[maxn];
set<int> s;//存储ollie的值

void xSort(int left,int right);
void ySort(int left,int right);//纵坐标优先排序
void swap(Point* a,Point *b);
void init();

struct Node{
  int l,r;//离散化纵坐标范围
  int value;//该范围内的点数
};
class SegTree{
private:
  Node node[maxn*4];
public:
  void buildTree(int l,int r,int index);
  int query(int l,int r,int index);
  void insert(int disy,int index);//离散化y坐标为disy的点数量+1
};
void SegTree::buildTree(int l,int r,int index){
  node[index].l=l;
  node[index].r=r;
  node[index].value=0;
  if(l!=r){
    int mid=(l+r)/2;
    buildTree(l,mid,index<<1);
    buildTree(mid+1,r,index<<1|1);
  }
}
int SegTree::query(int l,int r,int index){
  if(l<=node[index].l && r>=node[index].r) return node[index].value;
  int mid=(node[index].l+node[index].r)/2;
  int sum=0;
  if(r>=node[index].l && l<=mid) sum+=query(l,r,index<<1);
  if(l<=node[index].r && r>=mid+1) sum+=query(l,r,index<<1|1);
  return sum;
}
void SegTree::insert(int disy,int index){
  if(node[index].l==node[index].r){
    if(node[index].l==disy) node[index].value++;
    return;
  }
  int mid=(node[index].l+node[index].r)/2;
  if(disy>=node[index].l && disy<=mid){
    insert(disy,index<<1);
    node[index].value++;
  }
  else if(disy>=(mid+1) && disy<=node[index].r){
    insert(disy,index<<1|1);
    node[index].value++;
  }
}

SegTree seg_tree;
int main(){
  while(scanf("%d,",&n) && n){
    for(int i=0;i<n;i++) scanf("%d%d",&p[i].x,&p[i].y);//id:0~(n-1)
    init();
    ySort(0,n-1);
    int disy=1;
    discritizedY[p[0].id]=disy;//纵坐标离散化

    for(int i=1;i<n;i++){
      if(p[i].y==p[i-1].y){
        discritizedY[p[i].id]=discritizedY[p[i-1].id];
        _left[p[i].id]=_left[p[i-1].id]+1;
      }else{
        discritizedY[p[i].id]=++disy;
      }
    }//循环结束以后,disy为离散化以后最大的y坐标


    int _same=1;
    for(int i=n-2;i>=0;i--){
      if(p[i].y!=p[i+1].y){
        _large[ p[i].id ]=_large[ p[i+1].id ]+_same;
        _same=1;
      }else{
        _large[p[i].id]=_large[p[i+1].id];
        _same++;
        _right[p[i].id]=_right[p[i+1].id]+1;
      }
    }

    xSort(0,n-1);

    for(int i=1;i<n;i++){
      if(p[i].x==p[i-1].x) _down[p[i].id]=_down[p[i-1].id]+1;
    }
    for(int i=n-2;i>=0;i--){
      if(p[i].x==p[i+1].x) _up[p[i].id]=_up[p[i+1].id]+1;
    }
    seg_tree.buildTree(1,disy,1);
    int stan_max=INT_MIN;//stan最坏情况下的最高得分
    int stanx_min=INT_MAX;//一条线上stan的最少得分
    for(int i=0;i<n;){
      int current_x=p[i].x;
      int BL,TL,TR,BR;
      vector<int> ollie_nums;
      //在stan的一条线上,找stan的最小得分
      ollie_nums.clear();//对应的ollie的得分
      stanx_min=INT_MAX;
      while(p[i].x == current_x){
        BL=seg_tree.query(1,discritizedY[p[i].id],1)-_left[p[i].id]-_down[p[i].id];
        TL=i-BL-_left[p[i].id]-_down[p[i].id];
        TR=_large[p[i].id]-_up[p[i].id]-TL;
        BR=n-1-BL-TL-TR-_left[p[i].id]-_up[p[i].id]-_right[p[i].id]-_down[p[i].id];
        int stan_score=TR+BL;
        int ollie_score=TL+BR;

        /*
        cout<<"x="<<p[i].x<<"  ,y="<<p[i].y<<endl;
        cout<<"BL="<<BL<<" ,TL="<<TL<<" ,TR="<<TR<<" BR="<<BR<<endl;
        cout<<"stan_score="<<stan_score<<" ,ollie_score="<<ollie_score<<endl;
        */
        if(stan_score<stanx_min){
          ollie_nums.clear();
          stanx_min=stan_score;
          ollie_nums.push_back(ollie_score);
        }else if(stan_score==stanx_min) ollie_nums.push_back(ollie_score);
        //把新遍历的点插入
        seg_tree.insert(discritizedY[p[i].id],1);
        i++;
      }
      if(stanx_min>stan_max){
        stan_max=stanx_min;
        s.clear();
        for(vector<int>::iterator it=ollie_nums.begin();it!=ollie_nums.end();it++) s.insert(*it);
      }else if(stanx_min==stan_max){
        for(vector<int>::iterator it=ollie_nums.begin();it!=ollie_nums.end();it++) s.insert(*it);
      }
    }

    cout<<"Stan: "<<stan_max<<"; Ollie: ";
    set<int>::iterator l=s.end();
    l--;
    for(set<int>::iterator it=s.begin();it!=l;it++) cout<<*it<<" ";
    cout<<*l<<";"<<endl;

  }
}
void init(){
  for(int i=0;i<n;i++){
    discritizedY[i]=_left[i]=_right[i]=_down[i]=_up[i]=_large[i]=0;
    p[i].id=i;
  }
}

void ySort(int left,int right){
  if(left==right) return;
  else if(right==(left+1)){
    if(p[left].y>p[right].y) swap(&p[left],&p[right]);
    else if(p[left].y==p[right].y){
      if(p[left].x>p[right].x) swap(&p[left],&p[right]);
    }
  }else{
    int mid=(left+right)/2;
    ySort(left,mid);
    ySort(mid+1,right);

    int i=left,j=mid+1;
    int k=0;
    while(i<=mid && j<=right){
      if(p[i].y<p[j].y) tmp[k++]=p[i++];
      else if(p[i].y>p[j].y) tmp[k++]=p[j++];
      else{
        if(p[i].x<p[j].x) tmp[k++]=p[i++];
        else tmp[k++]=p[j++];
      }
    }
    while(i<=mid) tmp[k++]=p[i++];
    while(j<=right) tmp[k++]=p[j++];
    for(int m=0;m<k;m++) p[left+m]=tmp[m];
  }
}

//横坐标优先排序,p[left]-p[right]
void xSort(int left,int right){
  Point t;
  if(left==right) return;
  else if(right==(left+1)){
    if(p[left].x>p[right].x){
      swap(&p[left],&p[right]);
    }else if(p[left].x==p[right].x){
      if(p[left].y>p[right].y){
        swap(&p[left],&p[right]);
      }
    }
  }else{
    int mid=(left+right)/2;
    xSort(left,mid);
    xSort(mid+1,right);

    int i=left,j=mid+1;
    int k=0;
    while(i<=mid && j<=right){
      if(p[i].x<p[j].x) tmp[k++]=p[i++];
      else if(p[i].x>p[j].x) tmp[k++]=p[j++];
      else{
        if(p[i].y<p[j].y) tmp[k++]=p[i++];
        else tmp[k++]=p[j++];
      }
    }
    while(i<=mid) tmp[k++]=p[i++];
    while(j<=right) tmp[k++]=p[j++];

    for(int m=0;m<k;m++) p[left+m]=tmp[m];
  }
}

void swap(Point* a,Point *b){
  Point tmp;
  tmp=(*a);
  (*a)=(*b);
  (*b)=tmp;
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值