[关闭]
@dxbdly 2021-03-03T14:47:05.000000Z 字数 6729 阅读 211

G2020 19寒假集训Day1

信息学——19寒假集训


今天是G2020寒假集训Day1,只学了两东西。

主要内容:分治左偏树

注:本文引用清华大学 艾雨青的讲课PPT


一.分治

1.思想

  1. 1、将问题分解成若干更小的子问题
  2. 2、使用子问题的结果求出原问题的结果
  3. 即分解问题——>解决子问题——>合并子问题得到全局解

2. 最近点对问题

注:引用博客

题面

问题分析:

暴力:

时间复杂度

伪代码:

  1. minDist = infinity
  2. for i = 1 to length(P) - 1
  3. for j = i + 1 to length(P)
  4. let p = P[i], q = P[j]
  5. if dist(p, q) < minDist:
  6. minDist = dist(p, q)
  7. closestPair = (p, q)
  8. return closestPair

分治法:

观察题目,不难看出本题具有最优子结构性质

所以可用分治法解决

第一步:分解

对所有点按x坐标从小到大排序,并根据下标分割为两个集合

第二步:解决子问题

递归寻找两个集合中的最近点对。

第三步:合并子问题的解,得到全局解

BUT

这样显然会有问题:

  1. 在合并时最近距离不一定存在于两个集合中,
  2. 可能一个点在集合A,一个点在集合B,而这两点间距离小于dis

所以说,关键就在如何合并,我们可以取中点横坐标
取出区间 ,则易得,如果存在最小距离点对,一定在此区间内。

那么如何确定此区间中有没有解呢?

暴力:

根据左边区域 的点,遍历右边区域 的点,即可找到是否存在小于距离的点对。

时间复杂度最坏

枚举六个点

的最近点对, 在带域左半部分,则 点必在下图所示的 长方形上,而在该长方形上,最多只能由右边点集的6个点。每个点对之间的距离不小于

下面给出证明:

分成6个 的长方形

用反证法来证明:

假设存在大于6个点,则必有一个或多个小长方形存在两个及以上点

而小长方形的最长距离是为对角线长度,为:

最长距离都小于,与之前的条件不符合,故最多有6个点。

时间复杂度

AC代码:

  1. //The code is from dxbdly
  2. #include<bits/stdc++.h>
  3. using namespace std;
  4. inline int read()
  5. {
  6. int x=0;
  7. bool f=0;
  8. char c=getchar();
  9. while (!isdigit(c))
  10. f|=(c=='-'),c=getchar();
  11. while (isdigit(c))
  12. x=(x<<3)+(x<<1)+(c^48),c=getchar();
  13. return f?-x:x;
  14. }
  15. int n;
  16. struct node{
  17. double x,y;
  18. }point[200005],cnt[200005];
  19. int len;
  20. inline double get_dis(node a,node b)
  21. {
  22. return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
  23. }
  24. inline bool cmp1(node a,node b)
  25. {
  26. return a.x<b.x;
  27. }
  28. inline bool cmp2(node a,node b)
  29. {
  30. return a.y<b.y;
  31. }
  32. inline double calc(int l,int r)
  33. {
  34. if (r-l+1==2)
  35. return get_dis(point[l],point[r]);
  36. if (r-l+1==3)
  37. return min(get_dis(point[l],point[l+1]),get_dis(point[l+1],point[r]));
  38. int mid=(l+r)>>1;
  39. double dis=fmin(calc(l,mid),calc(mid+1,r));
  40. len=0;
  41. for (register int i=l;i<=r;++i)
  42. if (fabs(point[mid].x-point[i].x)<dis)
  43. cnt[++len]=point[i];
  44. stable_sort(cnt+1,cnt+len+1,cmp2);
  45. for (register int i=1;i<=len;++i)
  46. for (register int j=i+1;j<i+3&&j<=len;++j)
  47. dis=fmin(dis,get_dis(cnt[i],cnt[j]));
  48. return dis;
  49. }
  50. int main(){
  51. n=read();
  52. for (register int i=1;i<=n;++i)
  53. scanf("%lf%lf",&point[i].x,&point[i].y);
  54. stable_sort(point+1,point+n+1,cmp1);
  55. printf("%0.4lf",calc(1,n));
  56. return 0;
  57. }

二.左偏树

题面

我们会发现,此题中不仅需要维护很多堆,而且需要支持合并操作,这时普通堆解决不了,我们就要使用——左偏树

他长这个样子:

1.左偏树定义及性质

定义:

  1. 左偏树是一种可并堆
  2. 它除了支持优先队列的三个基本操作(插入,删除,取最小节点)
  3. 还支持一个很特殊的操作——合并操作。

性质:

  1. 左偏树满足堆性质,既任意节点都比它的后代小。
  2. 除此之外,左偏树还满足左偏性质。
  3. 定义一棵左偏树中的外节点为左子树或右子树为空的节点。
  4. 定义节点 i 的距离dist(i) 为节点 i 到它的后代中,最近的外节点所经过的边数。
  5. 任意节点的左子节点的距离不小于右子节点的距离(左偏性质)。

由此性质可得推论:

  1. 由左偏性质可知
  2. 1.一个节点的距离等于以该节点为根的子树最右路径的长度。
  3. 2.若左偏树的距离为一定值,则节点数最少的左偏树是完全二叉树。
  4. 3.若一棵左偏树的距离为k,则这棵左偏树至少有2k+1-1个节点。
  5. 4.一棵N个节点的左偏树距离最多为log(N+1)-1

2.左偏树基本操作

合并(merge)

对于两颗左偏树:(以小根堆为例),根分别为,,我们递归进行合并操作

如果 先将的右儿子与合并,若合并后
<将左右子树交换。

如果 将两树交换,使其变成

示例图:

添加(add)

将新加入的节点与原树合并即可。

删除根节点(delete)

将左右儿子合并成新树,将根节点直接删除即可。

注意

左偏树的添加,删除操作常与并查集连用(寻找某节点所在树的树根),再做完操作后记得对应更新并查集的值。

AC代码:

  1. //The code is from dxbdly
  2. #include<bits/stdc++.h>
  3. using namespace std;
  4. inline int read()
  5. {
  6. int x=0;
  7. char c=getchar();
  8. bool f=0;
  9. while (!isdigit(c))
  10. f|=(c=='-'),c=getchar();
  11. while (isdigit(c))
  12. x=(x<<3)+(x<<1)+(c^48),c=getchar();
  13. return f?-x:x;
  14. }
  15. int n,m;
  16. struct node{
  17. int id,w;
  18. }point[100005];
  19. int father[100005],lc[100005],rc[100005],dist[100005];
  20. bool b[100005];
  21. inline int Find(int x)
  22. {
  23. if (father[x]!=x)
  24. return father[x]=Find(father[x]);
  25. return father[x];
  26. }
  27. inline bool check(node a,node b)
  28. {
  29. return a.w!=b.w?a.w<b.w:a.id<b.id;
  30. }
  31. inline int merge(int x,int y)
  32. {
  33. if (!x||!y)
  34. return x+y;
  35. if (check(point[y],point[x]))
  36. swap(x,y);
  37. rc[x]=merge(rc[x],y);
  38. if (dist[lc[x]]<=dist[rc[x]])
  39. swap(lc[x],rc[x]);
  40. dist[x]=dist[rc[x]]+1;
  41. return x;
  42. }
  43. inline void update(int x,int y)
  44. {
  45. if (b[x]||b[y])
  46. return ;
  47. x=Find(x),y=Find(y);
  48. if (x!=y)
  49. father[x]=father[y]=merge(x,y);
  50. }
  51. inline void query(int x)
  52. {
  53. if (b[x])
  54. {
  55. printf("-1\n");
  56. return ;
  57. }
  58. x=Find(x),b[x]=1;
  59. printf("%d\n",point[x].w);
  60. father[x]=father[lc[x]]=father[rc[x]]=merge(lc[x],rc[x]);
  61. lc[x]=rc[x]=dist[x]=0;
  62. }
  63. int main(){
  64. n=read(),m=read();
  65. dist[0]=-1;
  66. for (register int i=1;i<=n;++i)
  67. father[i]=point[i].id=i,point[i].w=read();
  68. for (register int i=1;i<=m;++i)
  69. {
  70. int op=read(),x,y;
  71. if (op==1)
  72. x=read(),y=read(),update(x,y);
  73. else
  74. x=read(),query(x);
  75. }
  76. return 0;
  77. }

三.例题:

1. P2713 罗马游戏

题面

分析一下就可以看出这题是一个裸的左偏树,没什么好讲的

AC代码:

  1. //The code is from dxbdly
  2. #include<bits/stdc++.h>
  3. using namespace std;
  4. inline int read()
  5. {
  6. int x=0;
  7. char c=getchar();
  8. bool f=0;
  9. while (!isdigit(c))
  10. f|=(c=='-'),c=getchar();
  11. while (isdigit(c))
  12. x=(x<<3)+(x<<1)+(c^48),c=getchar();
  13. return f?-x:x;
  14. }
  15. int n,m;
  16. struct node{
  17. int id,w;
  18. }point[1000005];
  19. bool b[1000005];
  20. int father[10000005],lc[1000005],rc[1000005],dist[1000005];
  21. inline int Find(int x)
  22. {
  23. if (father[x]!=x)
  24. return father[x]=Find(father[x]);
  25. return father[x];
  26. }
  27. inline bool check(node x,node y)
  28. {
  29. return x.w!=y.w?x.w<y.w:x.id<y.id;
  30. }
  31. inline int merge(int x,int y)
  32. {
  33. if (!x||!y)
  34. return x+y;
  35. if (check(point[y],point[x]))
  36. swap(x,y);
  37. rc[x]=merge(rc[x],y);
  38. if (dist[lc[x]]<=dist[rc[x]])
  39. swap(lc[x],rc[x]);
  40. dist[x]=dist[rc[x]]+1;
  41. return x;
  42. }
  43. inline void update(int x,int y)
  44. {
  45. if (b[x]||b[y])
  46. return ;
  47. x=Find(x),y=Find(y);
  48. if (x!=y)
  49. father[x]=father[y]=merge(x,y);
  50. }
  51. inline void query(int x)
  52. {
  53. if (b[x])
  54. {
  55. printf("0\n");
  56. return ;
  57. }
  58. x=Find(x),b[x]=1;
  59. printf("%d\n",point[x].w);
  60. father[x]=father[lc[x]]=father[rc[x]]=merge(lc[x],rc[x]);
  61. lc[x]=rc[x]=dist[x]=0;
  62. }
  63. int main(){
  64. char op;
  65. dist[0]=-1;
  66. n=read();
  67. for (register int i=1;i<=n;++i)
  68. point[i].id=father[i]=i,point[i].w=read();
  69. m=read();
  70. for (register int i=1;i<=m;++i)
  71. {
  72. while (!isalpha(op=getchar()));
  73. int x,y;
  74. if (op=='M')
  75. x=read(),y=read(),update(x,y);
  76. else
  77. x=read(),query(x);
  78. }
  79. return 0;
  80. }

2. P1456 Monkey King

题面

解法:

此题要求我们在将两颗左偏树修改根节点的权值并合并,然后输出新根节点的权值。

由于两颗左偏树的根节点被修改,性质可能被破坏,所以我们可以先将修改后的左偏树维护好,在做合并操作即可。

那么如何修改(update):

  1. 实际上修改一颗左偏树根节点的权值,就相当于先将做删除(delet)操作,将根节点删除,然后将修改权值后的根节点插入(add)

剩下的就是左偏树基本操作啦!

AC代码:

  1. //The code is from dxbdly
  2. #include<bits/stdc++.h>
  3. using namespace std;
  4. inline int read()
  5. {
  6. int x=0;
  7. char c=getchar();
  8. bool f=0;
  9. while (!isdigit(c))
  10. f|=(c=='-'),c=getchar();
  11. while (isdigit(c))
  12. x=(x<<3)+(x<<1)+(c^48),c=getchar();
  13. return f?-x:x;
  14. }
  15. int n,m;
  16. int val[100005],father[100005],lc[100005],rc[100005],dist[100005];
  17. inline int Find(int x)
  18. {
  19. if (father[x]!=x)
  20. return father[x]=Find(father[x]);
  21. return father[x];
  22. }
  23. inline int merge(int x,int y)
  24. {
  25. if (!x||!y)
  26. return x+y;
  27. if (val[x]<val[y])
  28. swap(x,y);
  29. rc[x]=merge(rc[x],y);
  30. if (dist[lc[x]]<dist[rc[x]])
  31. swap(lc[x],rc[x]);
  32. dist[x]=dist[rc[x]]+1;
  33. return x;
  34. }
  35. inline int update(int x)
  36. {
  37. val[x]>>=1;
  38. int root=father[lc[x]]=father[rc[x]]=merge(lc[x],rc[x]);
  39. lc[x]=rc[x]=dist[x]=0;
  40. return father[x]=father[root]=merge(x,root);
  41. }
  42. inline void query(int x,int y)
  43. {
  44. x=Find(x),y=Find(y);
  45. if (x==y)
  46. {
  47. printf("-1\n");
  48. return ;
  49. }
  50. x=update(x),y=update(y);
  51. int root=father[x]=father[y]=merge(x,y);
  52. printf("%d\n",val[root]);
  53. }
  54. inline void clear()
  55. {
  56. memset(father,0,sizeof(father));
  57. memset(dist,0,sizeof(dist)),memset(val,0,sizeof(val));
  58. memset(lc,0,sizeof(lc)),memset(rc,0,sizeof(rc));
  59. }
  60. int main(){
  61. while (scanf("%d",&n)!=EOF)
  62. {
  63. clear();
  64. dist[0]=-1;
  65. for (register int i=1;i<=n;++i)
  66. father[i]=i,val[i]=read();
  67. m=read();
  68. for (register int i=1;i<=m;++i)
  69. {
  70. int x=read(),y=read();
  71. query(x,y);
  72. }
  73. }
  74. return 0;
  75. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注