suxxsfe

一言(ヒトコト)

P3295 [SCOI2016]萌萌哒

https://www.luogu.com.cn/problem/P3295
https://darkbzoj.tk/problem/4569

题目描述

一个长度为 \(n\) 的大数,用 \(S_1S_2S_3 \cdots S_n\) 表示,其中 \(S_i\) 表示数的第 \(i\) 位,\(S_1\) 是数的最高位。
告诉你一些限制条件,每个条件表示为四个数,\(l_1,r_1,l_2,r_2\),即两个长度相同的区间,表示子串 \(S_{l_1}S_{l_1+1}S_{l_1+2} \cdots S_{r_1}\)\(S_{l_2}S_{l_2+1}S_{l_2+2} \cdots S_{r_2}\) 完全相同。

比如 \(n=6\) 时,某限制条件 \(l_1=1,r_1=3,l_2=4,r_2=6\),那么 \(123123123123,351351351351\) 均满足条件,但是 \(1201212012,131141131141\) 不满足条件,前者数的长度不为 \(6\),后者第二位与第五位不同。问满足以上所有条件的数有多少个。
\(n,m\le 10^5\)


首先,可以想到把区间的相等转换为两个区间中的每一位对应相等,用并查集,将这些相等的位置放入同一集合
设最后一共有 \(x\) 个集合,那么答案应该是 \(9\cdot 10^{x-1}\),因为这是一个数,那第一位所在的那个集合就不能为 \(0\)
这样是 \(O(nm)\),得 30 分

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<map>
#include<iomanip>
#include<cstring>
#define reg register
#define EN std::puts("")
#define LL long long
inline int read(){
	register int x=0;register int y=1;
	register char c=std::getchar();
	while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
	while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
	return y?x:-x;
}
#define mod 1000000007
#define N 100005
int fa[N];
int n,m;
int find(int k){return k==fa[k]?k:fa[k]=find(fa[k]);}
inline void merge(int x,int y){
	x=find(x);y=find(y);
	if(x==y) return;
	fa[x]=y;
}
int main(){
	n=read();m=read();
	for(reg int i=1;i<=n;i++) fa[i]=i;
	for(reg int l1,r1,l2,i=1;i<=m;i++){
		l1=read();r1=read();l2=read();read();
		for(reg int j=0;j<=r1-l1;j++) merge(l1+j,l2+j);
	}
	int num=0;
	for(reg int i=1;i<=n;i++) num+=(find(i)==i);
	LL ans=1;
	for(reg int i=1;i<num;i++) ans=ans*10%mod;
	ans=ans*9%mod;
	printf("%lld",ans);
	return 0;
}

再考虑满分的做法,实际上是在前一种暴力的基础上,加了一个倍增的优化(也可以说是把并查集放到了 st 表上)
定义 \(fa_{j,i}\) 是从 \(i\) 开始,长度为 \(2^j\) 的区间,在并查集中的父亲的左端点
比如规则中有 \([x,x+2^k-1],[y,y+2^k-1]\) 这两个区间相等,那么将 \([x,x+2^k-1]\) 合并到另一个由 \(y\) 开始的区间上,\(fa_{k,x}=y\)
那么就可以以 \(O(\log^2 n)\) 的复杂度对给出的每一个规则进行并查集操作

计算答案前,将所有层(就是下表里的每个 \(j\) 在这里说成一“层”),对应的点合并
\((i,j-1)\)\((find(j,i),j-1)\) 合并:\(find(j,i)\) 是从 \(i\) 开始的 \(2^j\) 的区间的根个左端点,那么从他开始的 \(2^{j-1}\) 长度的区间,应该和从 \(i\) 开始的 \(2^{j-1}\) 长度的区间合并
\((i+2^{j-1},j-1)\)\((find(j,i)+2^{j-1},j-1)\) 合并,也是同理,就是上面描述的那两个长度 \(2^{j-1}\) 区间,在整个长度为 \(2^j\) 的区间中,剩下的一半
这样就只用统计有多少 \(find(0,i)=i\) 就行了

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<map>
#include<iomanip>
#include<cstring>
#define reg register
#define EN std::puts("")
#define LL long long
inline int read(){
	register int x=0;register int y=1;
	register char c=std::getchar();
	while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
	while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
	return y?x:-x;
}
#define mod 1000000007
#define N 100005
int fa[25][N];
int n,m;
int find(int i,int k){return k==fa[i][k]?k:fa[i][k]=find(i,fa[i][k]);}
inline void merge(int x,int y,int len){
	x=find(len,x);y=find(len,y);
	if(x==y) return;
	fa[len][x]=y;
}
int main(){
	n=read();m=read();
	for(reg int j=0;j<=20;j++)for(reg int i=1;i<=n;i++) fa[j][i]=i;
	for(reg int l1,r1,l2,i=1;i<=m;i++){
		l1=read();r1=read();l2=read();read();
		for(reg int j=20;~j;j--)if(l1+(1<<j)-1<=r1)
			merge(l1,l2,j),l1+=(1<<j),l2+=(1<<j);
	}
	for(reg int j=20;j;j--)
		for(reg int i=1;i+(1<<j)-1<=n;i++)
			merge(i,find(j,i),j-1),merge(i+(1<<(j-1)),fa[j][i]+(1<<(j-1)),j-1);
	int num=0;
	for(reg int i=1;i<=n;i++) num+=(find(0,i)==i);
	LL ans=1;
	for(reg int i=1;i<num;i++) ans=ans*10%mod;
	ans=ans*9%mod;
	printf("%lld",ans);
	return 0;
}
posted @ 2020-07-31 18:29  suxxsfe  阅读(116)  评论(0编辑  收藏  举报