zoukankan      html  css  js  c++  java
  • 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,mle 10^5)


    首先,可以想到把区间的相等转换为两个区间中的每一位对应相等,用并查集,将这些相等的位置放入同一集合
    设最后一共有 (x) 个集合,那么答案应该是 (9cdot 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;
    }
    
  • 相关阅读:
    Spring个人冲刺阶段总结
    开学测试代码
    《人月神话》读后感(三)
    《人月神话》读后感(二)
    《人月神话》读后感(一)
    记账本小程序7天开发记录(第七天)
    记账本小程序7天开发记录(第六天)
    记账本小程序7天开发记录(第五天)
    记账本小程序7天开发记录(第四天)
    记账本小程序7天开发记录(第三天)
  • 原文地址:https://www.cnblogs.com/suxxsfe/p/13411568.html
Copyright © 2011-2022 走看看