前缀和学习指南

前置芝士

条件前缀和

二维前缀和

树上前缀和

点前缀和

s[i]表示从根节点到节点i的点权和。

先自顶向下计算出前缀和s[i],然后用前缀和拼凑出(x,y)的路径和。

求路径和公式:

\(s[x]+s[y]-s[lca]-s[fa[lca]]\)

img

边前缀和

设s[i]表示从根节点到节点i的边权和。

先自顶向下计算出前缀和s[i],然后用前缀和拼凑(x,y)的路径和。

两点路径和公式:

\(s[x]+s[y]-2s[lca]\)

img

最大正方形

[problem description]

在一个 n×m 的只包含 01 的矩阵里找出一个不包含 0 的最大正方形,输出边长。

[input]

输入文件第一行为两个整数n,m(1≤n,m≤100),接下来 n 行,每行m 个数字,用空格隔开,01

[output]

一个整数,最大正方形的边长。

[solved]

标准的二维前缀和模板题

求和

[problem description]

master 对树上的求和非常感兴趣。他生成了一棵有根树,并且希望多次询问这棵树上一段路径上所有节点深度的\(k\) 次方和,而且每次的\(k\) 可能是不同的。此处节点深度的定义是这个节点到根的路径上的边数。他把这个问题交给了pupil,但pupil 并不会这么复杂的操作,你能帮他解决吗?

[input]

第一行包含一个正整数\(n\),表示树的节点数。

之后\(n-1\) 行每行两个空格隔开的正整数\(i, j\),表示树上的一条连接点\(i\) 和点\(j\) 的边。

之后一行一个正整数\(m\),表示询问的数量。

之后每行三个空格隔开的正整数\(i, j, k\),表示询问从点\(i\) 到点\(j\) 的路径上所有节点深度的\(k\) 次方和。由于这个结果可能非常大,输出其对\(998244353\) 取模的结果。

树的节点从\(1\) 开始标号,其中\(1\) 号节点为树的根。

[output]

对于每组数据输出一行一个正整数表示取模后的结果。

[样例]

5
1 2
1 3
2 4
2 5
2
1 4 5
5 4 45

[样例解释]

以下用\(d (i)\) 表示第\(i\) 个节点的深度。

对于样例中的树,有\(d (1) = 0, d (2) = 1, d (3) = 1, d (4) = 2, d (5) = 2\)

因此第一个询问答案为\((2^5 + 1^5 + 0^5)\ mod\ 998244353 = 33\),第二个询问答案为\((2^{45} + 1^{45} + 2^{45})\ mod\ 998244353 = 503245989\)

\(1 \leq n,m \leq 300000, 1 \leq k \leq 50\)

[solved]

倍增、树上前缀和

const ll mod = 998244353;
const int N = 3e5 + 10, M = 2 * N;
int n, m;
int h[N], to[M], ne[M], idx;
int fa[N][22];//fa[u][i]表示从u向上跳2^i层的祖先结点
int dep[N];   //dep[v]表示v的深度
ll mi[60];   //mi[j]表示dep[v]的j次幂
ll s[N][60]; //s[v][j]表示从根到v的路径节点的深度的j次幂之和

void add(int u, int v) {
  to[++idx] = v, ne[idx] = h[u], h[u] = idx;
}
void dfs(int u, int f) { //预处理节点信息
  for (int i = 1; i <= 20; i++)
    fa[u][i] = fa[fa[u][i - 1]][i - 1];
  for (int i = h[u]; i; i = ne[i]) {
    int v = to[i];
    if (v == f) continue;
    fa[v][0] = u; dep[v] = dep[u] + 1;
    for (int j = 1; j <= 50; j++) mi[j] = mi[j - 1] * dep[v] % mod;
    for (int j = 1; j <= 50; j++) s[v][j] = (mi[j] + s[u][j]) % mod;
    dfs(v, u);
  }
}
int lca(int u, int v) { //倍增法求lca
  if (dep[u] < dep[v])swap(u, v);//保证u节点的深度比v节点的深度深
  for (int i = 20; i >= 0; i--)
    if (dep[fa[u][i]] >= dep[v]) u = fa[u][i];
  if (u == v) return v;
  for (int i = 20; i >= 0; i--)
    if (fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
  return fa[u][0];
}
void solve() {
  cin >> n;
  for (int i = 1, a, b; i < n; i++) {
    cin >> a >> b;
    add(a, b);
    add(b, a);
  }
  mi[0] = 1; dfs(1, 0);
  cin >> m;
  for (int i = 1, u, v, k; i <= m; i++) {
    cin >> u >> v >> k;
    int l = lca(u, v);
    ll res = (s[u][k] + s[v][k] - s[l][k] - s[fa[l][0]][k] + 2 * mod) % mod;
    //减去两个小于mod的数,可能造成负数,最坏的情况会有res<(-mod)的情况,(res+2*mod)%mod可以转化为正整数
    cout << res << endl;
  }
}

粟粟的书架

[problem description]

幸福幼儿园 B29 班的粟粟是一个聪明机灵、乖巧可爱的小朋友,她的爱好是画画和读书,尤其喜欢 Thomas H. Cormen 的文章。粟粟家中有一个 \(R\)\(C\) 列的巨型书架,书架的每一个位置都摆有一本书,上数第 \(i\) 行、左数第 \(j\) 列摆放的书有 \(P_{i,j}\) 页厚。

粟粟每天除了读书之外,还有一件必不可少的工作就是摘苹果,她每天必须摘取一个指定的苹果。粟粟家果树上的苹果有的高、有的低,但无论如何凭粟粟自己的个头都难以摘到。不过她发现,如果在脚下放上几本书,就可以够着苹果;她同时注意到,对于第 \(i\) 天指定的那个苹果,只要她脚下放置书的总页数之和不低于 \(H_i\),就一定能够摘到。

由于书架内的书过多,父母担心粟粟一天内就把所有书看完而耽误了上幼儿园,于是每天只允许粟粟在一个特定区域内拿书。这个区域是一个矩形,第 \(i\) 天给定区域的左上角是上数第\(x1_i\) 行的左数第 \(y1_i\) 本书,右下角是上数第 \(x2_i\) 行的左数第 \(y2_i\) 本书。换句话说,粟粟在这一天,只能在这 \((x2_i-x1_i+1)\times(y2_i-y1_i+1)\) 本书中挑选若干本垫在脚下,摘取苹果。

粟粟每次取书时都能及时放回原位,并且她的书架不会再撤下书目或换上新书,摘苹果的任务会一直持续 \(M\) 天。给出每本书籍的页数和每天的区域限制及采摘要求,请你告诉粟粟,她每天至少拿取多少本书,就可以摘到当天指定的苹果。

[input]

第一行是三个正整数 \(R, C, M\)

接下来是一个 \(R\)\(C\) 列的矩阵,从上到下、从左向右依次给出了每本书的页数 \(P_{i,j}\)

接下来 \(M\) 行,第 \(i\) 行给出正整数 \(x1_i, y1_i, x2_i, y2_i, H_i\),表示第 \(i\) 天的指定区域是 \((x1_i, y1_i)\)\((x2_i, y2_i)\) 间的矩形,总页数之和要求不低于Hi。

保证 \(1\le x1_i\le x2_i\le R\)\(1\le y1_i\le y2_i\le C\)

[output]

输出共 \(M\) 行,第 \(i\) 行回答粟粟在第 \(i\) 天时为摘到苹果至少需要拿取多少本书。如果即使取走所有书都无法摘到苹果,则在该行输出 Poor QLW

[sample]

in

5 5 7
14 15 9 26 53
58 9 7 9 32
38 46 26 43 38
32 7 9 50 28
8 41 9 7 17
1 2 5 3 139
3 1 5 5 399
3 3 4 5 91
4 1 4 1 33
1 3 5 4 185
3 3 4 3 23
3 1 3 3 108

out

6
15
2
Poor QLW
9
1
3

[datas]

对于 \(50\%\) 的数据,满足 \(R, C\le 200\)\(M\le 2\times 10^5\)

\(1\le P_{i,j}\le 1000\)\(1\le H_i\le 2\times 10^9\)

[solved]

(1)这是一道组合算法题,在这里我关注于work1的算法过程

(2)预处理二维前缀和,二分书页范围,判定答案。

const int N = 500005;

int n, m, q;
int a[210][210], f[210][210][1010], g[210][210][1010];
//f[i][j][k]:区间[(1,1),(i,j)]内>=k的书的总页数
//f[i][j][k]:区间[(1,1),(i,j)]内>=k的总个数
int getsum(int x1, int y1, int x2, int y2, int k, int t) {
	if (t) return f[x2][y2][k] - f[x2][y1 - 1][k] - f[x1 - 1][y2][k] + f[x1 - 1][y1 - 1][k];
	else return g[x2][y2][k] - g[x2][y1 - 1][k] - g[x1 - 1][y2][k] + g[x1 - 1][y1 - 1][k];
}
void work1() {
	int mx = -1e9;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cin >> a[i][j];
			mx = max(a[i][j], mx);
		}
	}
	for (int k = 1; k <= mx; k++) {
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= m; j++) {
				f[i][j][k] = f[i - 1][j][k] + f[i][j - 1][k] - f[i - 1][j - 1][k] + (a[i][j] >= k) * a[i][j];
				g[i][j][k] = g[i - 1][j][k] + g[i][j - 1][k] - g[i - 1][j - 1][k] + (a[i][j] >= k);
			}
		}
	}
	// cout<<getsum(1,1,2,2,1,0)<<endl;
	int x1, y1, x2, y2, h;
	for (int i = 1; i <= q; i++) {
		cin >> x1 >> y1 >> x2 >> y2 >> h;
		if (getsum(x1, y1, x2, y2, 1, 1) < h) {cout << "Poor QLW" << endl; continue;}
		int l = 0, r = mx + 1;
		while (l + 1 < r) {
			int mid = (l + r) >> 1; //二分书页范围[1,mx]
			if (getsum(x1, y1, x2, y2, mid, 1) >= h) {
				l = mid;
			} else {
				r = mid;
			}
		}
		cout << getsum(x1, y1, x2, y2, l, 0) - (getsum(x1, y1, x2, y2, l, 1) - h) / l << endl;
	}
}

int b[N], root[N], tot; //root根节点,tot节点个数
int ls[N * 25], rs[N * 25], sum[N * 25], siz[N * 25];
//sum:区间内书的总页数
//siz:区间内书的总个数
void change(int &u, int v, int l, int r, int x) { //点修
	u = ++tot; //动态开点
	ls[u] = ls[v]; rs[u] = rs[v]; sum[u] = sum[v] + x; siz[u] = siz[v] + 1;
	if (l == r)return; //二分书页范围[1,mx]
	int mid = (l + r) >> 1;
	if (x <= mid)change(ls[u], ls[u], l, mid, x);
	else change(rs[u], rs[u], mid + 1, r, x);
}
int query(int u, int v, int l, int r, int h) { //点查
	if (l == r)return (h + l - 1) / l;
	int s = sum[rs[u]] - sum[rs[v]]; //二分书页范围[1,mx]
	int mid = (l + r) >> 1;
	if (h <= s)return query(rs[u], rs[v], mid + 1, r, h);
	else return query(ls[u], ls[v], l, mid, h - s) + siz[rs[u]] - siz[rs[v]];
}
void work2() { //可持久化线段树
	int mx = -1e9;
	for (int i = 1; i <= m; i++) cin >> b[i], mx = max(mx, b[i]);
	for (int i = 1; i <= m; i++)change(root[i], root[i - 1], 1, mx, b[i]);
	for (int i = 1, x1, y1, x2, y2, h; i <= q; i++) {
		cin >> x1; cin >> y1, y1 = root[y1-1]; cin >> x2; cin >> y2, y2 = root[y2]; cin >> h;
		if (sum[y2] - sum[y1] < h) {cout << "Poor QLW" << endl; continue;}
		cout << query(y2, y1, 1, mx, h) << endl;
	}
}

void solve() {
	cin >> n >> m >> q;
	if (n > 1) {
		work1();
	} else {
		work2();
	}
}
posted @ 2023-10-19 07:22  White_Sheep  阅读(1)  评论(0编辑  收藏  举报