[关闭]
@ysner 2018-06-10T09:11:59.000000Z 字数 2563 阅读 2001

天神下凡

并查集


题面

给出个圆心同在轴上的圆,问圆的轮廓将平面分为几块?(保证圆互不相交

解析

经过观察,可以发现,如果一个圆的直径被其它小圆的直径完全覆盖,就会产生额外一贡献。
因为圆在一条直线上,我们只用关心其在直线上的一部分,即可以用直径代替一个圆
一条直线再次被覆盖?能想到什么?
线段树?栈?

并查集

据说这是我的原创方法?
在手玩样例时,我发现,每次产生贡献的前一步都是把已联通(通过圆相切的方式)的两个点作为一个圆直径的两端点
如何判断两点是否联通?
每覆盖一条直线,两端点就连通了,此时两端点中间的点就没用了,直接把两端点纳入同一并查集即可。
当然,两端点联通有一种方式叫在同一圆直径的两端,即存在同圆,这会影响答案,要排序除掉。
由于半径范围(即直径两端点范围)过大,要用
于是复杂度达到了,但时间比为,不虚。(并查集+
可以通过离散化达到,时间比为
更有趣的是,如果不离散化而是用,时间比可达
(复习一下离散化部分)

  1. #include<iostream>
  2. #include<cmath>
  3. #include<cstring>
  4. #include<cstdio>
  5. #include<cstdlib>
  6. #include<algorithm>
  7. #define ll long long
  8. #define re register
  9. #define il inline
  10. #define fp(i,a,b) for(re int i=a;i<=b;i++)
  11. #define fq(i,a,b) for(re int i=a;i>=b;i--)
  12. using namespace std;
  13. const int N=3e5+100;
  14. int n,tot;
  15. struct node
  16. {
  17. ll l,r;
  18. bool operator < (const node &o) const {return (l<o.l)||(l==o.l&&r<o.r);}
  19. }a[N],b[N];
  20. ll f[N],ans=1,o[N<<1],top;
  21. bool vis[N<<1];
  22. il ll gi()
  23. {
  24. re ll x=0,t=1;
  25. re char ch=getchar();
  26. while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
  27. if(ch=='-') t=-1,ch=getchar();
  28. while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
  29. return x*t;
  30. }
  31. il int find(re int x){return x==f[x]?x:f[x]=find(f[x]);}
  32. int main()
  33. {
  34. freopen("god.in","r",stdin);
  35. freopen("god.out","w",stdout);
  36. n=gi();
  37. fp(i,1,n) f[i]=i;
  38. fp(i,1,n)
  39. {
  40. re ll x=gi(),r=gi();
  41. a[i].l=x-r,a[i].r=x+r;o[++top]=a[i].l;o[++top]=a[i].r;
  42. }
  43. sort(a+1,a+1+n);
  44. -----离散化-----
  45. sort(o+1,o+1+top);
  46. re int len=unique(o+1,o+1+top)-o-1;
  47. fp(i,1,n)
  48. {
  49. a[i].l=lower_bound(o+1,o+1+len,a[i].l)-o;
  50. a[i].r=lower_bound(o+1,o+1+len,a[i].r)-o;
  51. }
  52. -----离散化-----
  53. b[++tot]=a[1];
  54. fp(i,2,n)
  55. if(a[i-1]<a[i]) b[++tot]=a[i];
  56. fp(i,1,tot)
  57. {
  58. re int tag=0;
  59. re int u1=i,v1=vis[a[i].l],fu1=find(u1),fv1=find(v1);
  60. re int u2=i,v2=vis[a[i].r],fu2=find(u2),fv2=find(v2);
  61. if(v1&&v2&&fv1==fv2) ++ans;
  62. if(v1) f[fu1]=fv1;
  63. if(v2) f[fu2]=fv2;
  64. vis[a[i].l]=vis[a[i].r]=i;
  65. ++ans;
  66. }
  67. printf("%lld\n",ans);
  68. fclose(stdin);
  69. fclose(stdout);
  70. return 0;
  71. }

如何定义:

  1. #include<tr1/unordered_map>
  2. std::tr1::unordered_map<ll,int>vis;

把圆以左端点为第一关键字(从小到大),右端点为第二关键字(从大到小)排序。
想象一个圆,其中直径被若干个小圆覆盖。(这好像是产生贡献的唯一方式)
如果新加入一个小圆,与原有一个大圆内切于左端点,那么大圆就可能有贡献,入栈。
然后产生贡献的要求是覆盖不断且右端点相切
即如果新圆左端点大于栈顶圆右端点,就说明栈顶圆无贡献,弹出。
如果新圆右端点同栈顶圆右端点,产生贡献,弹出栈顶。
本来时间复杂度)
然而因为数据是按圆心从小到大给出,所以复杂度可达
时间比。比不得。。。

  1. n=read();
  2. for(rg int i=1,x,r;i<=n;i++)
  3. {
  4. x=read();
  5. r=read();
  6. c[i]=(circle){x-r,x+r};
  7. }
  8. sort(c+1,c+n+1,cmp);
  9. Ans=2;
  10. for(rg int i=2;i<=n;i++)
  11. {
  12. if(c[i].l==c[i-1].l&&c[i].r==c[i-1].r)continue;
  13. Ans++;
  14. if(c[i].l==c[i-1].l&&c[i].r<c[i-1].r)
  15. st[++top]=c[i-1];
  16. if(c[i].l>c[i-1].r)
  17. if(top)top--;
  18. if(c[i].r==st[top].r)Ans++,top--;
  19. }
  20. printf("%d\n",Ans);

线段树

从小往大加“直径边”,用线段树维护该段是否被完全覆盖过,如是则产生贡献。
复杂度为,时间比

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注