[BZOJ4569] [Luogu 3295] [SCOI2016]萌萌哒(并查集+倍增)

摘要:
[BZOJ4569][Luogu3295][SCOI2016]这个可爱的问题有一个n位小数a,并且给出了m个限制。每个限制表示该数字与该数字具有相同的位。我们将联合搜索集的1维更改为2维,表示区间的代表元素。当合并一个长度为(2^j)、起点为(x,y)的区间时,我们将该区间分成两半,然后再次合并,这样我们就可以得到for{for{//pushdown关系。长度为2^j的区间被推到2^(j-1)intf=S.find(j,i);S.merge;S.merge;}}}。时间复杂度是,但这里我偷了一个懒人,只写路径压缩并搜索集合,时间复杂度,因为常数很小,可以传递=fy)fa[b][fx]=fy;}}S整数2n;intn,m;Intmain(){intl1,l2,r1,r2;scanf;log2n=log2+1;S.ini;对于{scanf;intlen=r1-l1+1;对于{/二进制拆分,如果{S.merge;l1+=;l2+=;}}对于{//下推关系,2^j的长度被推到2^(j-1)intf=S.find(j,i);S.merge;对于{ifcnt++;}printf;}

[BZOJ4569] [Luogu 3295] [SCOI2016]萌萌哒(并查集+倍增)

题面

有一个n位的十进制数a(无前导0),给出m条限制,每条限制((l_1,r_1,l_2,r_2)(保证r_1-l_1=r_2-l_2))表示这个数的第([l_1,r_1])位与([l_2,r_2])位相同。问有多少个这样的数满足条件,答案取模(10^9+7),

(n leq 10^5)

分析

约定:我们把十进制数a的每一位从高到低称为第1位,第2位...,记为(a_i)

首先很妙的一点是,把区间限制转化成元素相等,即(a_{l_1+k}=a_{r_1+k}(k in [0,r_1-l_1+1]))。然后把所有相等的元素看作一个联通块,对于每条限制暴力合并,最后我们就会得到(cnt)个联通块。对于每个联通块,我们要给它的所有元素赋一个相同的值。包含(a_1)的联通块里的元素只能在([1,9])内取值(因为第一位不能是0),其他联通块的元素可以在([0,9])内取值。根据乘法原理,答案就是(9 imes 10^{cnt-1})

那么问题就是如何快速维护连通性。直接做是(O(n^2alpha(n)))的。注意到我们一个个合并([l_1,r_1])内的所有元素,很浪费时间,可以用倍增优化。

我们将并查集的1维改成2维,(f[i][j])表示区间([j,j+2^i-1])的代表元。合并的时候我们只需要把(r_1-l_1+1)二进制拆分,如13拆成(2^3+2^2+2^0).对于每一位(j),合并(f[j])上对应的一段长度为(2^j)的区间。

		int len=r1-l1+1;
		for(int j=log2n;j>=0;j--){//二进制拆分 
			if(len&(1<<j)){
				S.merge(j,l1,l2);
				l1+=(1<<j);
				l2+=(1<<j);
			} 
		}

[BZOJ4569] [Luogu 3295] [SCOI2016]萌萌哒(并查集+倍增)第1张

[图片来自mhy博客]

如下图,3的二进制拆分为(2^1+2^0)先合并长度为(2^1)的区间,再合并长度为(2^0)的区间

但是我们查询的时候需要查询的是每个点的信息,即(f[0][i]),所以我们要下推合并。合并长度为(2^j),起点分别为(x,y)的区间时,我们把区间断成两半,合并(f[j-1][x+2^{j-1}],f[j-1][y+2^{j-1}]),还要合并(f[j-1][x],f[j-1][y])这样做(O(log n))次,就可以得到(f[0][i])

	for(int j=log2n;j>=1;j--){
		for(int i=1;i+(1<<j)-1<=n;i++){//下推关系,长度为2^j的推到2^(j-1) 
			int f=S.find(j,i);
			S.merge(j-1,i,f);
			S.merge(j-1,i+(1<<(j-1)),f+(1<<(j-1))); 
		}
	}

时间复杂度为(O(n alpha(n)log n )),但是这里我偷了个懒,只写了路径压缩的并查集,时间复杂度(O(n log^2 n)),由于常数很小,可以通过。

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#define maxn 100000
#define maxlogn 21
#define mod 1000000007
using namespace std;
typedef long long ll;
inline ll fast_pow(ll x,ll k){
	ll ans=1;
	while(k){
		if(k&1) ans=ans*x%mod;
		x=x*x%mod;
		k>>=1;
	}
	return ans;
}

struct disjoint_set{
	int fa[maxlogn+5][maxn+5];//f[i][j]表示[j,j+2^i-1]的代表元
	void ini(int n){
		int k=log2(n)+1;
		for(int j=0;j<=k;j++){
			for(int i=1;i<=n;i++){
				fa[j][i]=i;
			}	
		}
	} 
	int find(int b,int x){
		if(fa[b][x]==x) return x;
		else return fa[b][x]=find(b,fa[b][x]);
	}
	void merge(int b,int x,int y){
		int fx=find(b,x);
		int fy=find(b,y);
		if(fx!=fy) fa[b][fx]=fy;
	}
}S;

int log2n;
int n,m;
int main(){
	int l1,l2,r1,r2;
	scanf("%d %d",&n,&m);
	log2n=log2(n)+1;
	S.ini(n);
	for(int i=1;i<=m;i++){
		scanf("%d %d %d %d",&l1,&r1,&l2,&r2);
		int len=r1-l1+1;
		for(int j=log2n;j>=0;j--){//二进制拆分 
			if(len&(1<<j)){
				S.merge(j,l1,l2);
				l1+=(1<<j);
				l2+=(1<<j);
			} 
		}
	}
	for(int j=log2n;j>=1;j--){
		for(int i=1;i+(1<<j)-1<=n;i++){//下推关系,长度为2^j的推到2^(j-1) 
			int f=S.find(j,i);
			S.merge(j-1,i,f);
			S.merge(j-1,i+(1<<(j-1)),f+(1<<(j-1))); 
		}
	}
	int cnt=0;
	for(int i=1;i<=n;i++){
		if(S.find(0,i)==i) cnt++;
	}
	printf("%lld
",9*fast_pow(10,cnt-1)%mod);
}

免责声明:文章转载自《[BZOJ4569] [Luogu 3295] [SCOI2016]萌萌哒(并查集+倍增)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇C# 与 SQLite的操作Http自动跳转Https的接口测试实践下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

基础树上问题 P5836 [USACO19DEC]Milk Visits S【并查集进行图的染色】

题目 https://www.luogu.com.cn/problem/P5836    分析 由于每个点的颜色选择范围都是两种,所以我们可以使用并查集来记录同一种颜色的连通块。 刚开始我的思路比较歪,只是单纯的考虑怎样能记录可以使朋友开心,后来想到可以考虑在怎样的情况下不能使朋友开心 具体看代码 代码 #include<iostream>...

C#中的DataTable简单使用Merge

//不同结构的DataTable追加第二个DataTable数据在对应行后的 简单使用//不同结构的DataTable追加在行后面的合并 DataTable dt = new DataTable(); dt.Columns.Add("ActivityID"); dt.Columns.Add("Activity...

DB2 replace into实现

最近进入到另一个项目, 数据库用的是DB2, 要实现MySQL中类似replace into的功能, 网上搜了下, 实现了一个类似功能的基础方法(PHP实现) public function replace($table, $arr, $pks){ $values = ''; $keys = '';...

UVA 11987 Almost Union-Find (并查集+删边)

  开始给你n个集合,m种操作,初始集合:{1}, {2}, {3}, … , {n} 操作有三种: 1 xx1 yy1 : 合并xx1与yy1两个集合 2 xx1 yy1 :将xx1元素分离出来合到yy1上 3 xx1 :查询xx1集合的元素个数,和元素所有值总和   并查集,1就是合并两个集合,3要记录两个权值。因为只要祖先的权值,所以Find操作不需...

Oracle中merge into的使用

该命令使用一条语句从一个或者多个数据源中完成对表的更新和插入数据. ORACLE 9i 中,使用此命令必须同时指定UPDATE 和INSERT 关键词,ORACLE 10g 做了如下改动。 1,insert 和update是可选的 2,UPDATE 和INSERT 后面可以跟WHERE 子句 3,在ON条件中可以使用常量来insert 所有的行到目标表中,...

MSSQL 插入数据时候,如果存在则更新的方法分享

摘要:下文讲述MSSQL中,插入数据时,如果存在则更新,否则就插入数据的方法分享实验环境:sql server 2017 mssql中,我们可以采用 MERGE INTO 关键字实现此功能,当两者匹配成功,则运行***语句,否则运行其它语句,达到插入数据时的判断操作,具体操作方法如下所示:  create table [maomao365.com] (k...