• [SCOI2016]萌萌哒(并查集+反向ST表(倍增))


    题意

    题目

    做法

    先说普通(O(n^2))做法。

    就是把相同的数字暴力合并,然后计算联通块个数(cnt),然后答案就是(10^{cnt-1}*9)

    但是呢,这样合并太太太太慢了,比如说([l,r])这个区间等于([q,p]),然后([l,r])又等于([frac{q+p}{2},?]),这样子的话,我们会跑(2(r-l+1)),但事实上,我们会发现,([l,frac{l+r}{2}],[frac{l+r}{2}+1,r],[q,frac{q+p}{2}],[frac{q+p}{2}+1,p],[p+1,?])这五个区间全部都是一样的,然后就只用跑(frac{5}{2}(r-l+1)),额,貌似还更慢了。。。但是如果这样子的区间越来越多,你就会发现,其会越来越快。

    因此不妨设(fa[i][j])([j,j+2^{i}))区间的并查集父亲。

    然后采用类似(ST)表的操作,不过是反向的,(ST)表是知道下面信息往上传,而这里是知道上面信息往下传。

    时间复杂度:(O(nlog^2n))

    #include<cstdio>
    #include<cstring>
    #define  N  110000
    #define  SN  20//16
    using  namespace  std;
    typedef  long  long  LL;
    const  LL  mod=1000000007;
    inline  int  findfa(int  x,int  *fa)
    {
    	if(fa[x]!=x)fa[x]=findfa(fa[x],fa);
    	return  fa[x];
    }
    inline  int  mymin(int  x,int  y){return  x<y?x:y;}
    inline  void  mer(int  x,int  y,int  *fa)
    {
    	int  tx=findfa(x,fa),ty=findfa(y,fa);
    	if(tx>ty)tx^=ty^=tx^=ty;
    	if(tx!=ty)fa[ty]=tx;
    }
    inline  LL  ksm(LL  x,int  y)
    {
    	LL  ans=1;
    	while(y)
    	{
    		if(y&1)ans=(ans*x)%mod;
    		x=(x*x)%mod;y>>=1;
    	}
    	return  ans;
    }
    int  fa[SN][N],lo2[N];
    int  n,m;
    int  main()
    {
    	scanf("%d%d",&n,&m);
    	for(int  i=2;i<=n;i++)lo2[i]=lo2[i>>1]+1;
    	for(int  i=1;i<=n;i++)
    	{
    		for(int  j=lo2[n-i+1];j>=0;j--)fa[j][i]=i;
    	}
    	for(int  i=1;i<=m;i++)
    	{
    		int  l1,r1,l2,r2;
    		scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
    		while(l1<=r1)
    		{
    			int  k=lo2[r1-l1+1];
    			mer(l1,l2,fa[k]);
    			l1+=(1<<k);
    			l2+=(1<<k);
    		}
    	}
    	for(int  i=lo2[n];i>=1;i--)
    	{
    		for(int  j=n-(1<<i)+1;j>=1;j--)
    		{
    			findfa(j,fa[i]);//事实上这个可以去掉,因为合并是把大的合并到小的里面 
    			mer(j,fa[i][j],fa[i-1]);mer(j+(1<<(i-1)),fa[i][j]+(1<<(i-1)),fa[i-1]);
    		}
    	}
    	int  cnt=0;
    	for(int  i=1;i<=n;i++)
    	{
    		if(findfa(i,fa[0])==i)cnt++;
    	}
    	LL  ans=(ksm(10,cnt-1)*9)%mod;
    	printf("%lld
    ",ans);
    	return  0;
    }
    

    或许你会问,我怎么知道什么时候要用到倍增啊????

    这里放上https://www.luogu.com.cn/blog/ShadowassIIXVIIIIV/solution-p3295大佬对于倍增使用的讲解。

    倍增
    其实倍增法不仅局限于快速幂,后缀数组,ST表,求lca之类的特定算法,而是用来解决一类特定问题的。
    
    倍增原理
    看似玄学的倍增其实源于两个数学等式
    
    1.2^n=2^(n-1)+2^(n-1)
    
    2.N=sigma(pi*2^i) [i∈(0,+∞)] {中pi为N的二进制第i位}
    
    想必任何一个知道二进制的人都明白上述两个等式吧……
    
    而倍增法的唯一要求的是,被称之为“+”的运算满足结合律(有些人也成操作满足可合并性)
    
    具体来讲,倍增法用到的地方一般是,我们现在要询问/修改一些东西,然后我们发现操作具有合并性,之后呢,我们对于每个大小为2^i的东西预处理出来一个答案,等到询问的时候,就将询问的东西大小用二进制表达,将预处理出来的每个东西合并起来就得到了答案
    
    常用的两类二进制拆分代码
    
    for(int i=0;p;p>>=1,i++){/*do sth*/}
    for(int i=20;p>0;i--){if(p>po[i]){p-=po[i]}/*do sth*/}
    
    其中上面一个适用于p已知的情况(比如倍增求lca,跳到深度相等的那段代码)
    
    下面的一个适用于p未知,但是可以比较p和某个值的大小,以及可以做相减运算 (比如lca第二段,两个点一起向上跳,但是不知道lca的深度的情况,但是我们用这个代码在未知lca深度的情况下对lca深度做了二进制拆分)
    
    然后我们来结合这道题讲一下如何魔改倍增法,因为这个玩意和dp,分治法都很像,都是根据某一个问题的性质设计了类似的"方法",根本不是有板子的算法,所以我们要具体问题具体分析……
    

    然后后面就是题解了。

  • 相关阅读:
    java中的Set的使用以及各种遍历方法(较为全面)
    系统图标
    监听按钮
    GUI
    【Avalon】获取隐藏元素的尺寸
    Hooks
    特性节点Attribute
    ngCloak
    邮件
    时间
  • 原文地址:https://www.cnblogs.com/zhangjianjunab/p/13890951.html
Copyright © 2020-2023  润新知