P3295 [SCOI2016]萌萌哒

P3295 [SCOI2016] 萌萌哒

题目

一个长度为 n n n的串,有 m m 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(r_1-l_1=r_2-l_2) l1,r1,l2,r2(r1l1=r2l2)表示,字串 [ l 1 , r 1 ] [l_1,r_1] [l1,r1]和字串 l 2 , r 2 l_2,r_2 l2,r2相等.你可以在除最高位的每一位填0123456789,最高位不能为0,问方案.

n , m ≤ 1 0 5 n,m\le 10^5 n,m105

思路

容易将问题转化为求字符串中不同的字符数量.

用并查集维护,用类似st表的方式优化.

我们设字符串中起始位置为 i i i,长度为 2 j 2^j 2j的字符串编号为 s t i , j st_{i,j} sti,j.它所在的集合为 f a s t i , j fa_{st_{i,j}} fasti,j.在同一个集合的字符串相等.

对于限制条件,我们可以二进制拆分原来的字符串,在并查集上维护.

处理完限制条件后,我们需要将相等关系下传,得到长度为1的字串的相等关系,从而解决这道题.

具体地说我们枚举一个以 i i i为起点,长度为 2 j 2^j 2j的字符串,把它分成两半:起点为 i i i,长度为 2 j − 1 2^{j-1} 2j1,以及起点为 i + 2 j − 1 i+2^{j-1} i+2j1,长度为 2 j − 1 2^{j-1} 2j1.我们在并查集中找到原串的相同串,设它的起始位置为 i d id id,(这一步可能需要一个额外的数组维护),也将它分为两半,起点为 i d id id,长度为 2 j − 1 2^{j-1} 2j1,以及起点为 i d + 2 j − 1 id+2^{j-1} id+2j1,长度为 2 j − 1 2^{j-1} 2j1.

可以得到两组相同串:起点为 i i i,长度为 2 j − 1 2^{j-1} 2j1的串和起点为 i d id id,长度为 2 j − 1 2^{j-1} 2j1的串,起点为 i + 2 j − 1 i+2^{j-1} i+2j1,长度为 2 j − 1 2^{j-1} 2j1的串和起点为 i d + 2 j − 1 id+2^{j-1} id+2j1,长度为 2 j − 1 2^{j-1} 2j1的串,将它们在并查集上么并即可.

代码

#include <iostream>
#include <cstdio>
#include <unordered_set>
using namespace std;
int read() {
	int re = 0;
	char c = getchar();
	bool negt = false;
	while(c < '0' || c > '9')negt |= (c == '-') , c = getchar();
	while(c >= '0' && c <= '9')re = (re << 1) + (re << 3) + c - '0' , c = getchar();
	return negt ? -re : re;
}
const int N = 1e5 + 10 , logN = 20;
struct DSU {
	int fa[N * logN];
	void init(int n) {
		for(int i = 1 ; i <= n ; i++)
			fa[i] = i;
	}
	int findroot(int x) {
		return fa[x] == x ? x : (fa[x] = findroot(fa[x]));
	}
	void merge(int x , int y) {
		if(findroot(x) != findroot(y))
			fa[findroot(x)] = findroot(y);
	}
} dsu;

int n , m;
int st[N][logN];
int indx[N * logN];//编号为i的串,起始位置为indx[i].
int log2[N];

unordered_set<int> s;

void init() {
	int cnt = 0;//编号
	for(int j = 0 ; (1 << j) <= n ; j++)
		for(int i = 1 ; i + (1 << j) - 1 <= n ; i++)
			st[i][j] = ++cnt , indx[cnt] = i;
	for(int i = 2 ; i <= n ; i++)
		log2[i] = log2[i - 1] + ((i & i - 1) == 0);
	dsu.init(cnt);
}

typedef long long ll;
ll mod = 1e9 + 7;
int poww(ll mul , int p) {
	int ans = 1;
	for( ; p ; p >>= 1)ans = (ans * ((p & 1) ? mul : 1)) % mod , mul = mul * mul % mod;
	return ans;
}
int main() {
	n = read() , m = read();
	init();
	for(int i = 1 ; i <= m ; i++) {
		int l1 = read() , r1 = read() , l2 = read() , r2 = read();
		int len = r1 - l1 + 1;
		int p = l1 , q = l2;
		for(int j = log2[len] ; j >= 0 ; j--) {
			if(p + (1 << j) - 1 > r1)continue;
			dsu.merge(st[p][j] , st[q][j]);
			p += (1 << j) , q += (1 << j);
		}
	}
	for(int j = log2[n] + 1 ; j > 0 ; j--) {
		for(int i = 1 ; i + (1 << j) - 1 <= n ; i++) {
			int id = indx[dsu.findroot(st[i][j])];
			dsu.merge(st[i][j - 1] , st[id][j - 1]);
			dsu.merge(st[i + (1 << j - 1)][j - 1] , st[id + (1 << j - 1)][j - 1]);
		}
	}
	for(int i = 1 ; i <= n ; i++)
		s.insert(dsu.findroot(i));
	printf("%lld" , 9ll * poww(10 , s.size() - 1) % mod);
//	cout << s.size();
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值