首页 > 其他分享 >题解——星之界

题解——星之界

时间:2022-11-30 22:55:42浏览次数:32  
标签:int 题解 复杂度 星之界 集合 prod id

题解

考虑化简这个可恶的式子

\[\prod\limits_{i = l}^{r} C_{\sum_{j = l}^{i}a_j}^{a_i} \]

拆开,设\(S\)为前缀和数组

\[=\prod^r_{i=l}\frac{(S_i-S_{l-1})!}{a_i!(S_i-S_{l-1}-a_i)!} \]

\[=\prod_{i=l}^rC_{S_{i-1}-S_{l-1}}^{S_i-S_{l-1}} \]

\[=\frac{\prod_{i=l}^r\left(S_i-S_{l-1}\right)!}{\left(\prod_{i=l}^ra_i!\right)\prod_{i=l-1}^{r-1}{(S_i-S_{l-1})!}} \]

\[=\frac{(S_r-S_{l-1})!}{\prod_{i=l}^ra_i!} \]

现在我们需要一个维护区间和和区间阶乘积并且支持区间定值修改的数据结构

貌似线段树之类的不行,考虑分块实际上是看见了1e5

统计比较容易,主要难点在于区间定值修改。注意到值域与\(n\)同级,考虑开桶来维护每个值,为了保证复杂度,对于每一个块开一个桶,空间复杂度\(O(N\sqrt N)\),勉强可以承受

但是一个桶只能表示这个值出现的数量,为了维护\(O(N\sqrt N )\)的复杂度,需要更好的工具来维护。或许可以类比懒标记的思想,延迟修改。那么我们只需要知道每个位置最终表示哪个数即可。如果把每个桶中的元素看作集合,那么区间定值修改就类似于合并集合,这启发我们联系并查集算法。那么最初将每个位置看做单独的集合,初始化块的时候将值相同的位置合并成一个集合,为了方便代码,可以选取每个值第一次出现的位置为代表元。在修改的时候,对于每一个块将表示值\(x\)的集合合并到\(y\)去,注意有可能\(y\)所在集合是空的,此时只需要将代表元的值更改即可。在散块更改的时候,可以直接暴力重置这个块。

核心代码

struct node {
	int id, cnt;
}f[S][N];//桶
struct Node {
	int sum, mul;
};
int get(int x) {
	return x == fa[x] ? x : fa[x] = get(fa[x]);
}
inline void init(int id) {//重置这个块
	mul[id] = 1;
	sum[id] = 0;
	for (re int i = L[id]; i <= R[id]; i++) {
		if (f[id][a[i]].id) {
			f[id][a[i]].cnt++;
			fa[i] = f[id][a[i]].id;
		}
		else {
			f[id][a[i]].id = i;
			fa[i] = i;
			val[i] = a[i];//val是真实值
			f[id][a[i]].cnt = 1;
		}
		sum[id] += a[i];
		mul[id] = 1ll * mul[id] * inv[a[i]] % p;
	}
}
inline void clear(int id) {
	for (re int i = L[id]; i <= R[id]; i++) {
		a[i] = val[get(i)];
		f[id][a[i]].cnt = f[id][a[i]].id = 0;
	}
	for (re int i = L[id]; i <= R[id]; i++) {
		fa[i] = 0;
	}
}
inline void change_only(int id, int l, int r, int x, int y) {
	clear(id);
	for (re int i = l; i <= r; i++) {
		if (a[i] == x)a[i] = y;
	}
	init(id);
}
inline void change_all(int id, int x, int y) {
	f[id][y].cnt += f[id][x].cnt;
	sum[id] -= (x - y) * f[id][x].cnt;
	mul[id] = 1ll * mul[id] * power_jc[x][f[id][x].cnt] % p * power_inv[y][f[id][x].cnt] % p;//为了保证复杂度,预处理阶乘及其逆元的幂
	if (f[id][y].id == 0)f[id][y].id = f[id][x].id, val[f[id][x].id] = y;
	else fa[f[id][x].id] = f[id][y].id;
	f[id][x] = { 0,0 };
}
inline void change(int l, int r, int x, int y) {
	if (pos[l] == pos[r]) {
		change_only(pos[l], l, r, x, y);
		return;
	}
	change_only(pos[l], l, R[pos[l]], x, y);
	change_only(pos[r], L[pos[r]], r, x, y);
	for (re int i = pos[l] + 1; i < pos[r]; i++)change_all(i, x, y);
}

查询的时候就很简单,将涉及到的积乘上,将和加上

inline Node find_only(int l, int r) {
	Node ans = { 0,1 };
	for (re int i = l; i <= r; i++) {
		ans.sum += val[get(i)];
		ans.mul = 1ll * ans.mul * inv[val[get(i)]] % p;
	}
	return ans;
}
inline Node find_all(int id) {
	return { sum[id],mul[id] % p };
}
inline Node merge(Node a, Node b) {
	return { a.sum + b.sum,1ll * a.mul * b.mul % p };
}
inline int find(int l, int r) {
	if (pos[l] == pos[r]) {
		Node ans = find_only(l, r);
		return 1ll * jc[ans.sum] * ans.mul % p;
	}
	Node ans = merge(find_only(l, R[pos[l]]), find_only(L[pos[r]], r));
	for (re int i = pos[l] + 1; i < pos[r]; i++) {
		ans = merge(ans, find_all(i));
	}
	return 1ll * jc[ans.sum] * ans.mul % p;
}

预处理的时候就常规预处理,然后处理阶乘逆元,预处理他们的幂保证复杂度(反正空间够),注意逆元得递推求

inline void init() {
	jc[1] = inv[1] = 1;
	for (re int i = 2; i <= M - 50; i++) {
		jc[i] = 1ll * jc[i - 1] * i % p;
		inv[i] = p - (1ll * p / (1ll * i) * 1ll * inv[p % i] % p) % p;
	}
	for (re int i = 2; i <= M - 50; i++) {
		inv[i] = 1ll * inv[i - 1] * inv[i] % p;
	}
	scanf("%d%d", &n, &m);
	for (re int i = 1; i <= n; i++)scanf("%d", &a[i]);
	block = sqrt(n);
	siz = n % block ? n / block + 1 : n / block;
	for (re int i = 1; i <= siz; i++) {
		L[i] = (i - 1) * block + 1;
		R[i] = min(i * block, n);
	}
	for (re int i = 1; i <= siz; i++) {
		for (re int j = L[i]; j <= R[i]; j++) {
			pos[j] = i;
		}
	}
	for (re int i = 1; i <= N - 5; i++) {
		power_jc[i][0] = power_inv[i][0] = 1;
		for (re int j = 1; j <= block; j++) {
			power_jc[i][j] = 1ll * power_jc[i][j - 1] * jc[i] % p;
			power_inv[i][j] = 1ll * power_inv[i][j - 1] * inv[i] % p;
		}
	}
	for (re int i = 1; i <= siz; i++)init(i);
}

注意有个细节(没注意到就是25pts,调我好久,还是看题解才发现的),当修改时\(x=y\),需要直接跳过不变,因为这会导致\(cnt\)等的值不正确

标签:int,题解,复杂度,星之界,集合,prod,id
From: https://www.cnblogs.com/oierpyt/p/16940079.html

相关文章

  • P8858题解
    折线题目描述平面直角坐标系的第一象限内有一块左下角为\((0,0)\)右上角为\((10^{100},10^{100})\)的矩形区域,区域内有正偶数个整点,试求出这样一条从\((0,0)\)出发......
  • 题解 P1902 刺杀大使
    题解P1902刺杀大使首先注意到,只需要到达一个开关,就可以开启所有开关(打开所有门)所以我们就可以想到,我们要寻找一条从任意\(1-m\)开关(因为访问一个开关就可以开启所有......
  • 你必须要知道的JavaScript数据结构与面试题解答
    英文原文|https://dev.to/educative/7-javascript-data-structures-you-must-know-4k0m原文作者|RyanThelin和AmandaFawcett译文翻译|web前端开发(web_qdkf)解决编码......
  • 【Java】Task07实验4第5题解析
    //TODO1:添加一个字段percent,用以表示百分秒privateintpercent;按照类的封装性要求,字段一般定义为私有的 //TODO2:添加一个只读属性getPercen......
  • 【题解】LOJ #6609 无意识的石子堆 - 感谢强大 alpha!!1【2】
    其实只有三个部分:环的EGF:\(\frac{1}{2}\sum\limits_{i\geq2}\frac{x^iy^i}{i}=\frac{1}{2}\left(\ln\frac{1}{1-xy}-xy\right)\)。链的EGF:\(\frac{1}{2}\frac{xy^2}......
  • 题解 UVA11244
    题解UVA11244题目大意:判断大小为1连通块有几个这个题说实话真的挺水的,你可以考虑用dfs来判断联通块然后记录大小这只是其中一个思路,另一个思路是,直接判断*的8连......
  • 题解 CF546C
    题解CF546Ccodeforces网址这个题看起来很难,其实是一个模拟题大体思路就是模拟每个人拿出手牌,并且比较,然后放入相应的人的手牌中的过程然后让我们想一下,如何才能便捷的......
  • 题解 CF1676G
    这个题标签里有树形dp,但是其实用dfs已经足以解决这道题。看这道题就可以发现这两道题其实是差不多的。首先需要给两个节点之间建边,我们需要从2到n循环输入。因为......
  • 题解 SP346
    题解SP346这个题的翻译貌似有点问题,这里的coins和goldcoins其实是一个东西有了这个前提,我们是再去看题面,就可以发现,这里的coins可以同时换成$\dfrac{n}{2}\\df......
  • 题解 CF1743B
    这个题是个简单的构造题因为不能有连续的“排列”,而排列序列都是必须是以\(1\)开头所以我们只要让\(2\)和\(1\)不相邻就能保证一个序列里只有它本身和\(1\)这两......