2022.8.17好题选讲

本文深入探讨了矩阵快速幂在解决等比数列求和问题中的应用,以及扫描线算法在处理矩形覆盖问题上的巧妙运用。通过实例分析和代码展示,详细解释了如何利用这两种算法高效地解决问题,旨在提升读者在算法设计和实现上的能力。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

T1 青染之心

 传送门:[Cnoi2019]青染之心

T2 polynomial

  传送门:polynomial

t a g tag tag矩阵快速幂???

分析

  这里让 O ( log ⁡ n ) O(\log n) O(logn) 求的式子:

∑ i = 0 n a n − i b i m o d    p \sum_{i = 0}^n a^{n - i}b^i \mod p i=0nanibimodp

  就相当于等比数列求和,那么这里的公比就是 b a \frac ba ab,首相是 b n b^n bn,答案就是:

a n s = a n ( 1 − ( b a ) n + 1 ) 1 − b a m o d    p ans = \frac{a^n(1 - (\frac ba)^{n + 1})}{1 - \frac ba} \mod p ans=1aban(1(ab)n+1)modp

  这里如果 p p p 是质数的话就可以 O ( log ⁡ n ) O(\log n) O(logn) 快速幂做完了,但是题目中不保证 p p p 是质数,那么就不能求逆元,所以我们需要另一种做法。

O ( log ⁡ n ) O(\log n) O(logn) 求式子的方法不多,快速幂不行我们就考虑矩阵快速幂。先考虑递推,令 f n f_n fn 表示答案,那么就有:

f n + 1 = a f n + b n + 1 f_{n + 1} = af_n +b^{n +1} fn+1=afn+bn+1

  所以我们就弄一个 [ f n b n + 1 ] \begin{bmatrix} f_n & b^{n +1} \end{bmatrix} [fnbn+1],这样的矩阵,然后每次:

[ f n b n + 1 ] [ a 0 1 b ] = [ f n + 1 b n + 2 ] \begin{bmatrix} f_n & b^{n +1} \end{bmatrix} \begin{bmatrix} a & 0 \\ 1 & b \end{bmatrix} = \begin{bmatrix} f_{n +1} & b^{n + 2} \end{bmatrix} [fnbn+1][a10b]=[fn+1bn+2]

  然后就是矩阵快速幂的板子了。

代码

// 卡常卡不过去 只有 30pts
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define in read()
#define MAXN 10
#define MAXM 10

inline ll read(){
	ll x = 0; char c = getchar();
	while(c < '0' or c > '9') c = getchar();
	while('0' <= c and c <= '9')
		x = x * 10 + c - '0', c = getchar();
	return x;
}

int T = 0;
ll n = 0, a = 0, b = 0, mod = 0;

struct Tmatrix{
	
	int n, m;
	ll a[MAXN][MAXM];
	
	void reset(){
		for(int i = 1; i <=n; i++)
			for(int j = 1; j <= m; j++) a[i][j] = 0;
	}
	void setIden(){
		if(n != m) return;
		for(int i = 1; i <= n; i++)
			for(int j = 1; j <= m; j++)
				if(i == j) a[i][j] = 1;
				else a[i][j] = 0;
	}

	Tmatrix operator * (const Tmatrix &rhs) const {
		Tmatrix ans; ans.n = n, ans.m = rhs.m; ans.reset();
		for(int k = 1; k <= m; k++)
			for(int i = 1; i <= ans.n; i++)
				for(int j = 1; j <= ans.m; j++)
					ans.a[i][j] = (ans.a[i][j] + a[i][k] * rhs.a[k][j] % mod) % mod;
		return ans;
	}
	void operator *= (const Tmatrix &rhs) { *this = *this * rhs; }

};

Tmatrix qp(Tmatrix a, ll b){
	Tmatrix ans; ans.n = ans.m = a.n; ans.setIden();
	for(; b; b >>= 1, a *= a) if(b & 1) ans *= a;
	return ans;
}

int main(){
	T = in;
	while(T--){
		n = in, a = in, b = in, mod = in;
		a %= mod, b %= mod;
		Tmatrix m1, m2;
		m1.n = 1, m1.m = 2, m2.n = m2.m = 2;
		m1.reset(), m2.reset();
		m1.a[1][1] = 1, m1.a[1][2] = b;
		m2.a[1][1] = a, m2.a[1][2] = 0, m2.a[2][1] = 1, m2.a[2][2] = b;
		cout << (m1 * (qp(m2, n))).a[1][1] << '\n';
	}
	return 0;
}

T3 窗口的星星

  传送门:窗口的星星

t a g tag tag扫描线???

分析

  现在已知矩形大小是固定的,所以我们显然可以用她的任意一个顶点来唯一确定这个矩形。所以现在我们考虑矩形的右上角应该放在哪儿。

  对于一个星星 ( x , y , c ) (x, y, c) (x,y,c) 来说,能圈住这个星星的矩形的右上角的范围是 X ∈ ( x , x + w ) , Y ∈ ( y , y + h ) X \in (x, x + w), Y \in (y, y +h) X(x,x+w)Y(y,y+h),这样一来,每一个星星就对应了一个矩形,这个矩形表示右上角在其中时会使得窗口能观测到这个星星。

  这里需要注意一个小细节,题目中说矩形的边界不算,所以我们把所有星星的坐标人为向左,向下各移动 0.5 0.5 0.5,在这个基础上,不妨设圈住星星的矩形的顶点坐标都是整数,于是左下角的坐标就可以看成 ( x , y ) (x, y) (x,y),右上角就可以看成 ( x + w , − 1 , y + h − 1 ) (x + w, - 1, y +h - 1) (x+w,1,y+h1),边界也计算进来,这样就能处理边界问题了。

  那么问题就转化成了平面上有若干个矩形,每个矩形有一个权值 c c c,求一个坐标使得这个坐标上重叠区域的权值最大。

  于是我们就可以使用扫描线了,做法跟矩形面积并差不多。

代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define in read()
#define MAXN 100100
#define ls(p) (p << 1)
#define rs(p) (p << 1 | 1)

inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' or c > '9') c = getchar();
	while('0' <= c and c <= '9')
		x = x * 10 + c - '0', c = getchar();
	return x;
}

int T = 0;
int n = 0, w = 0, h = 0;
int cor[MAXN << 1] = { 0 };

struct Tseg{
	int val;
	int l, r, h;
	bool operator < (const Tseg &rhs) const { return h != rhs.h ? h < rhs.h : val > rhs.val; }
}seg[MAXN << 1];

struct Tnode{
	int l, r;
	int dat, lazy;
}t[MAXN << 2];

void init(){
	memset(seg, 0, sizeof seg);
	memset(t, 0, sizeof t);
}

inline void up(int p) { t[p].dat = max(t[ls(p)].dat, t[rs(p)].dat); }
inline void down(int p){
	t[ls(p)].dat += t[p].lazy, t[rs(p)].dat += t[p].lazy;
	t[ls(p)].lazy += t[p].lazy, t[rs(p)].lazy += t[p].lazy;
	t[p].lazy = 0;
}

void build(int p, int l, int r){
	t[p].l = l, t[p].r = r, t[p].dat = t[p].lazy = 0;
	if(l == r) return;
	int mid = l + r >> 1;
	build(ls(p), l, mid), build(rs(p), mid + 1, r);
}

void update(int p, int l, int r, int val){
	if(l <= t[p].l and t[p].r <= r) { t[p].dat += val, t[p].lazy += val; return; }
	down(p);
	int mid = t[p].l + t[p].r >> 1;
	if(l <= mid) update(ls(p), l, r, val);
	if(r >  mid) update(rs(p), l, r, val);
	up(p);
}
int query() { return t[1].dat; }

signed main(){
	T = in;
	while(T--){
		n = in, w = in, h = in;
		for(int i = 1; i <= n; i++){
			int x = in, y = in, c = in;
			(i << 1)[cor] = y + h - 1;
			(i << 1)[seg] = (Tseg){ -c, y, y + h - 1, x + w - 1 };
			((i << 1) - 1)[cor] = y;
			((i << 1) - 1)[seg] = (Tseg){ c, y, y + h - 1, x };
		}
		n <<= 1;
		sort(cor + 1, cor + n + 1), sort(seg + 1, seg + n + 1);
		int m = unique(cor + 1, cor + n + 1) - cor - 1;
		for(int i = 1; i <= n; i++){
			seg[i].l = lower_bound(cor + 1, cor + m + 1, seg[i].l) - cor;
			seg[i].r = lower_bound(cor + 1, cor + m + 1, seg[i].r) - cor;
		} build(1, 1, m);
		int ans = 0;
		for(int i = 1; i <= n; i++){
			update(1, seg[i].l, seg[i].r, seg[i].val);
			ans = max(ans, query());
		}
		cout << ans << '\n';
	}
	return 0;
}

T4 星之器

  传送门:星之器

t a g tag tag势能???

分析

  注意到一个事实:最终得到的魔法值之和初状态和末状态有关,和怎样移动无关。那么也就是说让你输出最大的魔法值就是纯诈骗,我们现在就只需要考虑怎么快速通过初状态和末状态的信息求出魔法值就好了。

  想一想好像也不知道该怎么做,所以就要引入 t a g tag tag 里面的势能这个东西了。

  势能是什么,只要是上过高中的同学都应该知道(应该上过初中的也知道),并且势能有一个和这道题的魔法值非常接近的性质,也就是一个东西的势能和路径无关,之和初末状态有关,这也是高中物理中解决力学问题的一个重要思路。并且势能这个东西还有另一个重要的性质,也就是一个系统的势能是其中所有物品的势能之和。

  于是我们可以考虑类比势能来解决这道题,具体来说,给每一个点定义一个是能函数 f ( x , y ) f(x, y) f(x,y),满足整个局面的势能为(其中 n u m i , j num_{i, j} numi,j 表示 ( i , j ) (i, j) (i,j) 中星的数量):

∑ x = 1 n ∑ y = 1 m n u m i , j f ( x , y ) \sum_{x = 1}^n \sum_{y = 1}^m num_{i, j}f(x, y) x=1ny=1mnumi,jf(x,y)

  然后用末状态减初状态的势能就是答案。

  现在就是考虑怎样设计这个是能函数,我们容易发现,在 y y y 相同的时候有( x 2 > x 1 x_2 > x_1 x2>x1):

f ( x 1 , y ) + f ( x 2 , y ) − f ( x 1 + 1 , y ) − f ( x 2 − 1 , y ) = x 2 − x 1 − 1 f(x_1,y) + f(x_2, y) - f(x_1 + 1, y) - f(x_2 - 1, y) = x_2 - x_1 - 1 f(x1,y)+f(x2,y)f(x1+1,y)f(x21,y)=x2x11

  简单移个项:

f ( x 1 , y ) − f ( x 1 + 1 , y ) + x 1 + 1 = f ( x 2 − 1 , y ) − f ( x 2 , y ) + x 2 f(x_1, y) - f(x_1 + 1, y) + x_1 + 1 = f(x_2 - 1, y) - f(x_2, y) + x_2 f(x1,y)f(x1+1,y)+x1+1=f(x21,y)f(x2,y)+x2

  我们令 g ( x , y ) = f ( x , y ) − f ( x + 1 , y ) + x + 1 g(x, y) = f(x, y) - f(x + 1, y) + x + 1 g(x,y)=f(x,y)f(x+1,y)+x+1,那么等式就变成了:

g ( x 1 , y ) = g ( x 2 − 1 , y ) g(x_1, y) = g(x_2 - 1, y) g(x1,y)=g(x21,y)

  也就是说我们只需要让 g ( x , y ) g(x, y) g(x,y) 的数值对于相同的一个 y y y 都相等就行了。这也就是一个数列的二阶线性递推问题了,这个上过高中的同学也应该都会做吧qwq,所以我们就能很容易的得到在职考虑 x x x 的情况下:

f ( x , y ) = 1 2 x 2 f(x, y) = \frac 12 x^2 f(x,y)=21x2

  那么考虑 y y y 就和上面同理就做完了,于是我们得到:

f ( x , y ) = 1 2 ( x 2 + y 2 ) f(x, y) = \frac 12 (x^2 + y^2) f(x,y)=21(x2+y2)

  整道题就做完了。

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define in read()

inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' or c > '9') c = getchar();
	while('0' <= c and c <= '9')
		x = x * 10 + c - '0', c = getchar();
	return x;
}

int n = 0, m = 0;
ll ini = 0, fin = 0;

int main(){
	n = in, m = in;
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= m; j++)
			ini += in * (i * i + j * j);
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= m; j++)
			fin += in * (i * i + j * j);
	cout << (ini - fin >> 1) << '\n';
	return 0;
}

T5

T6

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值