a tutorial on computer science

  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  21 随笔 :: 0 文章 :: 17 评论 :: 0 Trackbacks
   acm两个题目,hdoj1521&hdoj2795。
   hdoj1521讲的是一群猴子,他们每一个都有一个力量值。刚刚开始猴子们是互相不认识的。每一只猴子都可能和别的猴子吵架,当吵架的时候,他们会把自己认识的所有的人的老大(力量值最大的那只猴子,当然包括自己)拉过来,两只力量值最大的猴子会协调他们,然后这两群猴子就认识了。代价是两只力量值最大的猴子减半。现在给出猴子们吵架的顺序和每只猴子的力量值,输出吵架之后那一堆猴子最大的力量值。题目详见:http://acm.hdu.edu.cn/showproblem.php?pid=1521
  看到这个题目首先想到了并查集。但是并查集重点描述的是每个节点之间的关系,或者说并查集的设计思想重点在节点关系上。如果要取得并查集中最大值,删除最大值,想不到好的办法。果断看discuss,左偏树+并查集。好吧。其实题目很明显有几种操作:取得最大值,删除节点,合并集合。取得最大值和删除节点是二叉堆的强项,但是二叉堆对于合并复杂度很差,O(N),而本题中用到合并的次数很多。So,左偏树。
  左偏树是个什么玩意呢?简单的说,就是可以合并的二叉树,复杂度为O(logN),并且满足堆的性质。(重点在可以合并堆,实际上,左偏树就是可并堆的一种实现,其余的实现还有Fib堆)。具体的介绍和证明在这篇论文上:左偏树的特点及其应用。百度之吧。大神已经讲得很清楚了。
 代码如下:
#include <stdio.h>
#include 
<string.h>
#define MAXN 500010

typedef 
struct
{
  
int l,r,d,strong;
  
int f;
}
node;

node heap[MAXN];

void swap(int& a,int& b)
{
  
int tmp;
  tmp 
= a;
  a 
= b;
  b 
= tmp;
}


int merge(int a,int b)
{
   
if(a == 0)
      
return b;
   
if(b == 0)
      
return a;

   
if(heap[a].strong < heap[b].strong)
      swap(a,b);
   
int res = merge(b,heap[a].r);
   heap[a].r 
= res;
   heap[res].f 
= a;
   
if(heap[heap[a].l].d < heap[heap[a].r].d)
      swap(heap[a].l,heap[a].r);
   
if(heap[a].r == 0)
      heap[a].d 
= 0;
   
else
      heap[a].d 
= heap[heap[a].r].d+1;
   
return a;
}


int findp(int i)
{
   
return i==heap[i].f?heap[i].f:findp(heap[i].f);
}


int pop(int n)
{
   
int l = heap[n].l;
   
int r = heap[n].r;

   heap[n].f 
= n;
   heap[n].l
=heap[n].r=heap[n].d=0;

   heap[l].f 
= l;
   heap[r].f 
= r;
   
return merge(l,r);     
}


int main()
{
   
int N,M;
   
while(scanf("%d",&N)!=EOF)
   
{
     
int i,a,b;
     
for(i=1;i<=N;i++)
     
{
      heap[i].l
=heap[i].r=heap[i].d=0;
      heap[i].f 
= i;
      scanf(
"%d",&heap[i].strong);
     }

     scanf(
"%d",&M);
     
for(i=0;i<M;i++)
     
{
        scanf(
"%d%d",&a,&b);
        
int m = findp(a);
        
int n = findp(b);
        
if(m==n)
          printf(
"-1\n");
        
else
        
{
          heap[m].strong
/=2;
          heap[n].strong
/=2;
          a 
= pop(m);
          a
= merge(a,m);
          b 
= pop(n);
          b 
=merge(b,n);
          
int root = merge(a,b);          
          printf(
"%d\n",heap[findp(root)].strong);
        }

     }

   }

   
return 0;
}


   第二个题,并查集。
   题目大意:开学了,有一个h*w黑板,上面是空的。现在有N张1*width的海报要贴在上面。规则是,行号越小越好,行号相同的情况下,越左边越好。很久以前看过这个题目,那时候没有好好想。误以为是个二维线段树巴拉巴拉的。。。现在想想,其实黑板的每一行只要用一个数组记录下来就好,然后把所有的列组织成线段树
记录所有线段的最大的长度。这样这个题目就转化成了线段树最简单的那种形式:区间记录一段节点信息,要注意的是,更新下层节点信息后要更改父节点的max值。
贴代码:
#include <stdio.h>
#define MAXN 1000000
#define max(a,b) a>b?a:b

typedef 
struct
{
 
int l,r,max,row;
}

node;
node data[MAXN];

int width,ROW;

void build(int i,int l,int r)
{
  data[i].l 
= l;
  data[i].r 
= r;
  
int mid = (l+r)/2;
  data[i].max 
= width;
  
if(l<r)
  
{
     build(
2*i,l,mid);
     build(
2*i+1,mid+1,r);
  }

  
else
  
{
      data[i].row 
= ROW;
      ROW
++;
  }

}



int query(int i,int l)

   
if(data[i].max<l)
      
return -1
   
if(data[i].l == data[i].r)
   
{
      data[i].max 
-= l;
      data[i
/2].max=max(data[i].max,data[i+1].max);
      
return data[i].row;
   }

   
if(data[2*i].max>=l)
     
{
      
int res = query(2*i,l);
      data[i].max
=max(data[2*i].max,data[2*i+1].max);
      
return res;
     }

   
else
     
{
      
int res = query(2*i+1,l);
      data[i].max
=max(data[2*i].max,data[2*i+1].max);       
      
return res;
     }

}



int main()
{
  
int h,w,n,i,tmp; 
  
while(scanf("%d%d%d",&h,&w,&n)!=EOF)
  
{
    ROW 
= 1;
    width 
= w;
    
if(h>210000)
      h 
= 210000;
    build(
1,1,h);
    
for(i=0;i<n;i++)
    
{
      scanf(
"%d",&tmp);
      printf(
"%d\n",query(1,tmp));
    }
        
  }

  
return 0;
}

左偏树的论文还没做证明。留着。
PSS:明天体侧,求RP~
posted on 2011-11-12 22:24 bigrabbit 阅读(349) 评论(0)  编辑 收藏 引用

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理