[usOJ6149]英语危机

题目

传送门

题目描述
有一条数字长链,这条链上有些数字是相同的,有些地方是不同的,这条数链有很多种不同的可能。求最多有多少种可能的方案?
为了简化题目,数链没有前导零。因为答案可能很大,所以你只需要输出答案 m o d    1 0 9 + 7 \mod10^9+7 mod109+7的结果。

输入输出格式
输入格式
第一行:两个正整数 N ( 1 ≤ N ≤ 100000 ) N(1\le N\le 100000) N(1N100000) M ( 1 ≤ M ≤ 100000 ) M(1\le M \le 100000) M(1M100000),表示这个数的长度为 N N N,提出了 M M M个要求。
接下来 M M M行:每行四个整数 l 1 , r 1 , l 2 , r 2 l_1,r_1,l_2,r_2 l1,r1,l2,r2,表示该数链的 [ l 1 , r 1 ] [l_1,r_1] [l1,r1] [ l 2 , r 2 ] [l_2,r_2] [l2,r2]相同。

输出格式
仅一行,一个整数,有多少种可能的情况。

思路

这两个相同的、长度为 l e n len len的区间,可以拆分为两个区间分别相等。
是否在哪里看过这种一个区间拆成两个区间的、高效的,类似的呢?—— R M Q \Bbb{RMQ} RMQ
这提示我们第二维 j j j表示区间长度为 2 j 2^j 2j。这样,就可以用 并查集 来存储每一个相同的了!
具体来说:将有序数对 ( i , j ) (i,j) (i,j)当做下标,存储其并查集中的父节点。
而父节点与子节点的长度相同,只需存储父节点的 first \text{first} first

统计答案时,最终所有 second \text{second} second不为 0 0 0的(也就是长度不为 2 0 = 1 2^0=1 20=1的),都要下传成长度为 1 1 1的(便于统计)。
那么我们一开始就传下去就好啦!有一种情况可以中途剪枝:想要固定相同的两个区间本来就 因为各种不可描述的原因 相同。

代码

#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
typedef long long LL;

const int LG = 18, Max = 100000, MOD = 1e9 + 7;

int N, M, Lg[Max+5], f[Max+5][LG];
bool vis[Max+5];
int findRoot(int x,int t){ // 并查集找根 
	if(not f[x][t])
		return x; // 路径压缩 
	return f[x][t] = findRoot(f[x][t],t);
}
void Merge(int a,int b,int t){
	int x = findRoot(a,t);
	int y = findRoot(b,t);
	if(x == y) return ; // 已经要求相同 
	f[x][t] = y;
	if(not t) return ; // 最底层 
	Merge(a,b,t-1);
	Merge(a+(1<<t>>1),b+(1<<t>>1),t-1);
}

int main(){
	cin >> N >> M;
	Lg[0] = -1, Lg[1] = 0;
	for(int i = 2; i <= N; ++ i)
		Lg[i] = Lg[i>>1]+1;
	for(int j=0; j<=Lg[N]; ++j)
		for(int i=1; i<=N; ++i)
			f[i][j] = 0; // 根节点 
	int a, b, c, d;
	for(int i = 1; i <= M; ++ i){
		cin >> a >> b >> c >> d;
		int t = Lg[b-a+1];
		Merge(a,c,t);
		Merge(b-(1<<t)+1,d-(1<<t)+1,t);
	}
	LL Ans = 9; // 去掉前导零 
	vis[findRoot(1,0)] = 1;
	for(int i=1; i<=N; ++i){
		int now = findRoot(i,0);
		if(not vis[now]){
			vis[now] = true;
			Ans = Ans*10%MOD;
		}
	}
	cout << Ans;
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值