ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

回文树(并查集)(倍增)(LCA)(ST 表)

2021-08-24 09:00:18  阅读:175  来源: 互联网

标签:le int 查集 ST fa LCA 倍增 deg


回文树

题目大意

给你一棵树,然后你要给每个点给上一个字母。
有一些限制条件,要求某一段路径在填好之后是一个回文串。
问你总有有多少种方案满足限制条件。

思路

首先不难从回文串中看出它就是让一些位置规定要字母相同。
那关系之间就只有相同和任意。
那你就需要找到有多少互补相干的,那这么多个 \(26\) 乘在一起就是答案了。

那接着不难想到用并查集,但你发现直接暴力维护就只能有 \(20\) 分。
那你考虑怎么优化,这也是这题最神仙的地方。
看到树上操作,自然想到倍增,然后再加上并查集。
那就会想到把并查集和倍增搞到一起!!!
具体就是把每个倍增的区间都维护一个并查集,然后跑完所有限制条件再把它们全部下降到长度为 \(1\)。

那接着你考虑看树上路径要怎么相互配对:
在这里插入图片描述
假设你要搞这条路径,我们把浅的到根以及他配对的找出来:
在这里插入图片描述
那接着另外一段也要匹配:
在这里插入图片描述
那你分别看这两段,棕色那段两段都是向上的,只要互相匹配就行了。
那你就搞一个倍增,把它分成 \(logn\) 段,然后两两相互配对。
接着麻烦的是粉色的那一段,你会发现一个是向上,一个是向下的。

那就不难想到对于倍增的每个区间要搞两个并查集,一个是维护正的,一个是维护反的。
然后你看两个加起来长度固定,而且你想你把一个并查集反复放入另一个并查集跟放一次没有影响,不难想到一个东西可以快速求——ST表!!!

然后我们接着讲讲要怎么合并。
在这里插入图片描述
这是两段你要合并的路径:
因为是倍增的,你把它分成两段:
在这里插入图片描述
那如果两个都是正的,那就是这么配对:
在这里插入图片描述
如果一正一反,就是这样:
在这里插入图片描述
也许有人会想,你这不是要继续递归吗?
没错是可以,但这样会超时,我们可以就把它放在这里先,然后等所有限制都跑了之后,就把它给下传,下传也是像这样子的规则下传。

然后不难看出到最后如果正的和反的的父亲如果有一个是自己,那就说明它就代表了一个独立的。

然后就能统计出来了。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define mo 1000000007

using namespace std;

struct node {
	int to, nxt;
}e[200001];
int n, x, y, m, le[100001], KK;
int deg[100001], fa[100001][21];
int tot, fath[2][100001][21];
int sz[5000001], d[5000001][3];
int log2[100001], father[5000001];

void add(int x, int y) {
	e[++KK] = (node){y, le[x]}; le[x] = KK;
	e[++KK] = (node){x, le[y]}; le[y] = KK;
}

//倍增的预备 dfs
void dfs(int now, int father) {
	deg[now] = deg[father] + 1;
	fa[now][0] = father;
	
	for (int i = le[now]; i; i = e[i].nxt)
		if (e[i].to != father) {
			dfs(e[i].to, now);
		}
}

//求 LCA
int LCA(int x, int y) {
	if (deg[y] > deg[x]) swap(x, y);
	for (int i = 20; i >= 0; i--)
		if (deg[fa[x][i]] >= deg[y])
			x = fa[x][i];
	if (x == y) return x;
	for (int i = 20; i >= 0; i--)
		if (fa[x][i] != fa[y][i])
			x = fa[x][i], y = fa[y][i];
	return fa[x][0];
}

int jump(int now, int high) {
	for (int i = 20; i >= 0; i--)
		if (high >= (1 << i)) {
			high -= (1 << i);
			now = fa[now][i];
		}
	return now;
}

//并查集
int find(int now) {
	if (father[now] == now) return now;
	return father[now] = find(father[now]);
}

//合并并查集
void up(int ox, int x, int oy, int y, int k) {
	int X = find(fath[ox][x][k]), Y = find(fath[oy][y][k]);
	if (X == Y) return ;
	if (sz[X] > sz[Y]) swap(X, Y);
	father[X] = Y;
	sz[Y] += sz[X];
}

void merge(int ox, int x, int oy, int y, int num) {
	if (ox == oy) {//两个都是正的
		for (int i = 20; i >= 0; i--)
			if (num >= (1 << i)) {
				num -= (1 << i);
				up(ox, x, oy, y, i);
				x = fa[x][i];
				y = fa[y][i];
			}
		up(ox, x, oy, y, 0);
		return ;
	}
	
	//一正一反
	if (ox == 1) {
		swap(ox, oy);
		swap(x, y);
	}
	int dis = deg[x] - deg[y];
	for (int i = 20; i >= 0; i--)
		if (dis >= (1 << i)) {
			dis -= (1 << i);
			int fry = fa[jump(x, dis)][0];
			up(ox, x, oy, fry, i);
			break;//这里找到就 break,所以是 ST 表
			//这个 dis 是两段的加起来要的长度,所以只要刚好小于它就可以了
		}
	up(ox, x, oy, y, 0);
}

//快速幂
ll ksm(ll x, int y) {
	ll re = 1;
	while (y) {
		if (y & 1) re = (re * x) % mo;
		x = (x * x) % mo;
		y >>= 1;
	}
	return re;
}

int main() {
//	freopen("paltree.in", "r", stdin);
//	freopen("paltree.out", "w", stdout);
	
	scanf("%d", &n);
	for (int i = 1; i < n; i++) {
		scanf("%d %d", &x, &y);
		add(x, y);
	}
	
	log2[0] = -1;
	for (int i = 1; i <= n; i++)
		log2[i] = log2[i >> 1] + 1;
	
	dfs(1, 0);
	for (int i = 1; i <= 20; i++)
		for (int j = 1; j <= n; j++)
			fa[j][i] = fa[fa[j][i - 1]][i - 1];
	
	for (int i = 0; i <= 1; i++)
		for (int j = 1; j <= n; j++)
			for (int k = 0; k <= 20; k++) {
				fath[i][j][k] = ++tot;
				sz[tot] = 1;
				father[tot] = tot;
				d[tot][0] = i; d[tot][1] = j; d[tot][2] = k;
			}//初始化
	
	scanf("%d", &m);
	for (int i = 1; i <= m; i++) {
		scanf("%d %d", &x, &y);
		int lca = LCA(x, y);
		if (deg[y] > deg[x]) swap(x, y);
		int nowrun = deg[y] - deg[lca];
		merge(0, x, 0, y, nowrun);//两个正的
		x = jump(x, nowrun);
		y = jump(y, nowrun);
		merge(0, x, 1, y, deg[x] - deg[y]);//一正一反
	}
	
	for (int i = 20; i >= 1; i--) {//把它下降会全部长度为 1 的
		for (int j = 1; j <= n; j++) {
			for (int k = 0; k <= 1; k++) {
				int x = fath[k][j][i];
				int X = find(x);
				if (x == X) continue;
				int x1 = k, x2 = j, x3 = i;
				int X1 = d[X][0], X2 = d[X][1], X3 = d[X][2];
				if (x1 == X1) {
					up(x1, x2, X1, X2, x3 - 1);
					up(x1, fa[x2][x3 - 1], X1, fa[X2][x3 - 1], x3 - 1);
				}
				else {
					if (x1 == 1) {
						swap(x1, X1);
						swap(x2, X2);
						swap(x3, X3);
					}
					up(x1, x2, X1, fa[X2][x3 - 1], x3 - 1);
					up(x1, fa[x2][x3 - 1], X1, X2, x3 - 1);
				}
				//注意这里也要分一正一反,两个正的
			}
		}
	}
	
	for (int i = 1; i <= n; i++)//最后一层
		up(0, i, 1, i, 0);
	
	int num = 0;//统计答案
	for (int i = 1; i <= n; i++)
		for (int j = 0; j <= 1; j++) {//正的或反的有一个可以就行
			if (find(fath[j][i][0]) == fath[j][i][0])
				num++;
		}
	
	printf("%lld", ksm(26, num));
	//记得你算出来的是互不相干的共多少个,所以答案是这么多个 26 乘在一起
	
	fclose(stdin);
	fclose(stdout);
	
	return 0;
}

标签:le,int,查集,ST,fa,LCA,倍增,deg
来源: https://www.cnblogs.com/Sakura-TJH/p/jzoj_4498.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有