垃圾ACMer的暑假训练220710

垃圾ACMer的暑假训练220710

蓝桥杯算法训练

2. 二分与前缀和

①二分前一个区间 A A A的右端点:将 [ l , r ] [l,r] [l,r]分为 [ l , m i d − 1 ] [l,mid-1] [l,mid1] [ m i d , r ] [mid,r] [mid,r].若 m i d mid mid满足 A A A的性质,则 a n s ans ans [ m i d , r ] [mid,r] [mid,r]中,否则 a n s ans ans [ l , m i d − 1 ] [l,mid-1] [l,mid1]中.

while(l < r) {
  int mid = l + r + 1 >> 1;  // +1避免l=r-1时死循环
  if(mid满足A的性质) l = mid;  // 判断标志
  else r = mid - 1;
}

②二分后一个区间 B B B的左端点:将 [ l , r ] [l,r] [l,r]分为 [ l , m i d ] [l,mid] [l,mid] [ m i d + 1 , r ] [mid+1,r] [mid+1,r].若 m i d mid mid满足 B B B的性质,则 a n s ans ans [ l , m ] [l,m] [l,m]中,否则 a n s ans ans [ m i d + 1 , r ] [mid+1,r] [mid+1,r].

while(l < r) {
  int mid = l + r >> 1;  // l=r-1时不会死循环
  if(mid满足B的性质) r = mid;  // 判断标志
  else l = mid + 1;
}

2.1 机器人跳跃 ( 3   s ) (3\ \mathrm{s}) (3 s)

题意

有编号 0 ∼ n    ( 1 ≤ n ≤ 1 e 5 ) 0\sim n\ \ (1\leq n\leq 1\mathrm{e}5) 0n  (1n1e5) ( n + 1 ) (n+1) (n+1)个建筑从左到右排列.编号为 0 0 0的建筑高度为 0 0 0,编号为 i    ( 1 ≤ i ≤ n ) i\ \ (1\leq i\leq n) i  (1in)的建筑高度为 h i    ( 1 ≤ h i ≤ 1 e 5 ) h_i\ \ (1\leq h_i\leq 1\mathrm{e}5) hi  (1hi1e5).初始时机器人在编号为 0 0 0的建筑处,每一步它将跳到右边的下一个建筑处.设机器人在第 k k k个建筑处,且它当前的能量为 E E E,下一步它将跳到第 ( k + 1 ) (k+1) (k+1)个建筑.若 h k + 1 > E h_{k+1}>E hk+1>E,则机器人失去 h k + 1 − E h_{k+1}-E hk+1E的能量,否则它得到 E − h k + 1 E-h_{k+1} Ehk+1的能量.游戏目标是到达第 n n n个建筑,途中能量不能为负值.求机器人至少以多少能量开始游戏才能保证成功完成游戏.

第一行输入一个 n n n.第二行输入 n n n个整数 h 1 , ⋯   , h n h_1,\cdots,h_n h1,,hn.

输出机器人所需的最小能量,答案上取整.

思路

h k + 1 > E h_{k+1}>E hk+1>E时, E − ( h k + 1 − E ) = 2 E − h k + 1 E-(h_{k+1}-E)=2E-h_{k+1} E(hk+1E)=2Ehk+1.

h k + 1 ≤ E h_{k+1}\leq E hk+1E时, E + ( E − h k + 1 ) = 2 E − h k + 1 E+(E-h_{k+1})=2E-h_{k+1} E+(Ehk+1)=2Ehk+1.

故两种情况的能量更新都是 2 E − h k + 1 2E-h_{k+1} 2Ehk+1,

初始能量具有单调性,即若初始能量为 E 0 E_0 E0时满足条件,则 ∀ E 0 ′ ≥ E \forall E_0'\geq E E0E也满足条件.

[] 设初始能量为 E 0 E_0 E0时,机器人到达第 1 ∼ n 1\sim n 1n个建筑处的能量依次为 E 1 , ⋯   , E n    ( E i ≥ 0 , 1 ≤ i ≤ n ) E_1,\cdots, E_n\ \ (E_i\geq 0,1\leq i\leq n) E1,,En  (Ei0,1in).

初始能量为 E 0 ′ E_0' E0时,注意到 E 1 ′ = 2 E 0 ′ − h 1 ≥ 2 E 0 − h 1 = E 1 E_1'=2E_0'-h_1\geq 2E_0-h_1=E_1 E1=2E0h12E0h1=E1,数归即证.

check()函数可递推一遍看中途会不会出现负值,但这样每次能量乘 2 2 2会爆long long.设建筑物高度的最大值为 m a x h maxh maxh.若 E ≥ m a x h E\geq maxh Emaxh,则 2 E − h i ≥ E + E − h i ≥ E + ( m a x h − h i ) ≥ E 2E-h_i\geq E+E-h_i\geq E+(maxh-h_i)\geq E 2EhiE+EhiE+(maxhhi)E,即能量单调增.故只需中途能量 E ≥ m a x h E\geq maxh Emaxh即可返回true.

代码
const int MAXN = 1e5 + 5;
int n;
int h[MAXN];
int maxh = -INF;

bool check(int e) {
	for (int i = 1; i <= n; i++) {
		e = e * 2 - h[i];
		if (e >= maxh) return true;
		if (e < 0) return false;
	}
	return true;
}

int main() {
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> h[i];
		maxh = max(maxh, h[i]);
	}

	int l = 0, r = 1e5;
	while (l < r) {
		int mid = l + r >> 1;
		if (check(mid)) r = mid;
		else l = mid + 1;
	}
	cout << l;
}


2.2 四平方和

题意

输入一个正整数 n    ( 0 < n < 5 e 6 ) n\ \ (0<n<5\mathrm{e}6) n  (0<n<5e6),求 0 ≤ a ≤ b ≤ c ≤ d   s . t .   n = a 2 + b 2 + c 2 + d 2 0\leq a\leq b\leq c\leq d\ s.t.\ n=a^2+b^2+c^2+d^2 0abcd s.t. n=a2+b2+c2+d2,对所有的 ( a , b , c , d ) (a,b,c,d) (a,b,c,d)升序排列,输出字典序最小的表示法.

思路

注意到 a , b , c , d ≤ n ≤ 2232 a,b,c,d\leq \sqrt{n}\leq 2232 a,b,c,dn 2232,则最多只能枚举两个数.

可先枚举较大的 c c c d d d,记录下所有 c 2 + d 2 c^2+d^2 c2+d2可能的取值.再枚举较小的 a a a b b b,计算 t = n − a 2 − b 2 t=n-a^2-b^2 t=na2b2,判断 t t t是否在 c 2 + d 2 c^2+d^2 c2+d2中出现过,可用哈希 O ( 1 ) O(1) O(1)或二分 O ( log ⁡ n ) O(\log n) O(logn)实现,二分的时间复杂度 5 e 6 × lg ⁡ ( 5 e 6 ) ≈ 1 e 8 5\mathrm{e}6\times \lg(5\mathrm{e}6)\approx 1\mathrm{e}8 5e6×lg(5e6)1e8,但枚举时 a ≤ b ≤ c ≤ d a\leq b\leq c\leq d abcd,故复杂度约要除以 2 2 2,可过.

注意到 a a a 0 0 0开始枚举, b b b a a a开始枚举,若找到解,则 ( a , b ) (a,b) (a,b)字典序最小.对 { c 2 + d 2 , c , d } \{c^2+d^2,c,d\} {c2+d2,c,d}升序排列即可保证解的 ( c , d ) (c,d) (c,d)字典序最小.

总时间复杂度:①暴力枚举 O ( n 3 ) O(n^3) O(n3);②哈希 O ( n 2 ) O(n^2) O(n2);③二分 O ( n 2 log ⁡ n ) O(n^2\log n) O(n2logn).实际运行中二分最快,因为哈希表常数大.

代码I:暴力枚举(TLE)
int n;

int main() {
	cin >> n;
	
	for (int a = 0; a * a <= n; a++) {
		for (int b = a; a * a + b * b <= n; b++) {
			for (int c = b; a * a + b * b + c * c <= n; c++) {
				int t = n - a * a - b * b - c * c;
				int d = sqrt(t);
				if (d * d == t) 
					return cout << a << ' ' << b << ' ' << c << ' ' << d << endl, 0;
			}
		}
	}
}

代码II:二分
const int MAXN = 2.5e6 + 5;  // 5e6 / 2
int n;
struct Sum {
	int s, c, d;  // s=c^2+d^2
	
	bool operator<(const Sum& p)const {
		if (s != p.s) return s < p.s;
		else if (c != p.c) return c < p.c;
		else return d < p.d;
	}
}sum[MAXN];

int main() {
	cin >> n;
	
	// 预处理出所有c^2+d^2
	int idx = 0;
	for (int c = 0; c * c <= n; c++)
		for (int d = c; c * c + d * d <= n; d++)
			sum[idx++] = { c * c + d * d,c,d };
	sort(sum, sum + idx);

	for (int a = 0; a * a <= n; a++) {
		for (int b = a; a * a + b * b <= n; b++) {
			int t = n - a * a - b * b;
			
			// 二分是否存在c^2+d^2=t
			int l = 0, r = idx - 1;
			while (l < r) {
				int mid = l + r >> 1;
				if (sum[mid].s >= t) r = mid;
				else l = mid + 1;
			}
			if (sum[l].s == t)  // 判断是否存在
				return cout << a << ' ' << b << ' ' << sum[l].c << ' ' << sum[l].d << endl, 0;
		}
	}
}

代码III:哈希表(TLE)
int n;
umap<int, pii> s;  // s[c^2+d^2]={c,d}

int main() {
	cin >> n;
	
	// 预处理出所有c^2+d^2
	int idx = 0;
	for (int c = 0; c * c <= n; c++) {
		for (int d = c; c * c + d * d <= n; d++) {
			int t = c * c + d * d;
			if (!s.count(t)) s[t] = { c,d };  // 只记录第一个出现的c^2+d^2
		}
	}

	for (int a = 0; a * a <= n; a++) {
		for (int b = a; a * a + b * b <= n; b++) {
			int t = n - a * a - b * b;
			
			if (s.count(t)) 
				return cout << a << ' ' << b << ' ' << s[t].first << ' ' << s[t].second << endl, 0;
		}
	}
}


2.3 分巧克力

题意

k    ( 1 ≤ k ≤ 1 e 5 ) k\ \ (1\leq k\leq 1\mathrm{e}5) k  (1k1e5)个小朋友和有 n    ( 1 ≤ n ≤ 1 e 5 ) n\ \ (1\leq n\leq 1\mathrm{e}5) n  (1n1e5)块巧克力,其中第 i    ( 1 ≤ i ≤ n ) i\ \ (1\leq i\leq n) i  (1in)块是 h i × w i    ( 1 ≤ h i ≤ w i ≤ 1 e 5 ) h_i\times w_i\ \ (1\leq h_i\leq w_i\leq 1\mathrm{e}5) hi×wi  (1hiwi1e5)的长方形.现需从 n n n块巧克力中切出 k k k块分给小朋友,要求巧克力形状是边长为整数的大小相同的正方形.求每个小朋友能得到的最大边长.数据保证每个小朋友至少能获得 1 × 1 1\times 1 1×1的正方形.

思路

边长为 w i × h i w_i\times h_i wi×hi能切出边长为 x x x的正方形 ⌊ w i x ⌋ ⌊ h i x ⌋ \left\lfloor\dfrac{w_i}{x}\right\rfloor\left\lfloor\dfrac{h_i}{x}\right\rfloor xwixhi个.显然边长越大,能切出来的块数越小,即具有单调性.显然可二分出块数 ≥ k \geq k k的最大边长.

check()函数需扫一遍 n n n块巧克力.总时间复杂度 O ( n log ⁡ max ⁡ { h i , w i } ) O(n\log\max\{h_i,w_i\}) O(nlogmax{hi,wi}).

代码
const int MAXN = 1e5 + 5;
int n, k;  // 巧克力数、人数
int h[MAXN], w[MAXN];

bool check(int m) {
	int res = 0;
	for (int i = 0; i < n; i++) {
		res += (h[i] / m) * (w[i] / m);
		if (res >= k)return true;
	}
	return false;
}

int main() {
	cin >> n >> k;
	for (int i = 0; i < n; i++) cin >> h[i] >> w[i];

	int l = 1, r = 1e5;
	while (l < r) {
		int mid = l + r + 1 >> 1;
		if (check(mid)) l = mid;
		else r = mid - 1;
	}
	cout << l;
}


2.4 激光炸弹 ( 168   M B 168\ \mathrm{MB} 168 MB)

题意

地图上有 n    ( 1 ≤ n ≤ 1 e 4 ) n\ \ (1\leq n\leq 1\mathrm{e}4) n  (1n1e4)个目标,其中第 i    ( 1 ≤ i ≤ n ) i\ \ (1\leq i\leq n) i  (1in)个目标的坐标为 ( x i , y i )    ( 0 ≤ x i , y i ≤ 5000 ) (x_i,y_i)\ \ (0\leq x_i,y_i\leq 5000) (xi,yi)  (0xi,yi5000),价值为 w i    ( 0 ≤ w i ≤ 1000 ) w_i\ \ (0\leq w_i\leq 1000) wi  (0wi1000),不同目标可能在同一位置.激光炸弹可摧毁 r × r    ( 1 ≤ r ≤ 1 e 9 ) r\times r\ \ (1\leq r\leq 1\mathrm{e}9) r×r  (1r1e9)的正方形的所有目标,其中炸弹只能放在整点上,且爆炸范围的正方形的边平行于 x x x y y y轴.求一颗炸弹能炸掉的目标的最大总价值.

思路

r r r最多取 5001 5001 5001即可覆盖整个地图.若地图不够 r × r r\times r r×r的正方形大,则可能会出现边界问题,故让地图的行数和列数至少为 r r r.

暴力枚举各以 ( i , j ) (i,j) (i,j)为右下角的、边长为 r r r的正方形内的和.

若开一个 5000 × 5000 5000\times 5000 5000×5000的原数组和一个 5000 × 5000 5000\times 5000 5000×5000的前缀和数组,则需 2 × 500 0 2 × 4 1024 × 1024 ≈ 200   M B \dfrac{2\times 5000^2\times 4}{1024\times 1024}\approx 200\ \mathrm{MB} 1024×10242×50002×4200 MB,会MLE,故只能开一个前缀和数组.

代码
const int MAXN = 5005;
int n, m;  // 行数、列数
int pre[MAXN][MAXN];

int main() {
	int cnt, r; cin >> cnt >> r;
	r = min(5001, r);

	n = m = r;  // 防边界问题

	while (cnt--) {
		int x, y, w; cin >> x >> y >> w;
		x++, y++;  // 下标从1开始
		n = max(n, x), m = max(m, y);
		pre[x][y] += w;
	}

	for (int i = 1; i <= n; i++)  // 预处理前缀和
		for (int j = 1; j <= m; j++)
			pre[i][j] += pre[i - 1][j] + pre[i][j - 1] - pre[i - 1][j - 1];

	int ans = 0;
	for (int i = r; i <= n; i++)  // 枚举以(i,j)为右下角的边长为r的正方形
		for (int j = r; j <= m; j++)
			ans = max(ans, pre[i][j] - pre[i - r][j] - pre[i][j - r] + pre[i - r][j - r]);
	cout << ans;
}


2.5 k倍区间

题意

对长度为 n    ( 1 ≤ n ≤ 1 e 5 ) n\ \ (1\leq n\leq 1\mathrm{e}5) n  (1n1e5)的数列 a 1 , ⋯   , a n    ( 1 ≤ a i ≤ 1 e 5 ) a_1,\cdots,a_n\ \ (1\leq a_i\leq 1\mathrm{e}5) a1,,an  (1ai1e5),若其中一段连续的子序列 a i , ⋯   , a j a_i,\cdots,a_j ai,,aj之和是 k    ( 1 ≤ k ≤ 1 e 5 ) k\ \ (1\leq k\leq 1\mathrm{e}5) k  (1k1e5)的倍数,则称区间 [ i , j ] [i,j] [i,j] k k k倍区间.求序列中 k k k倍区间的个数.

思路

枚举每个区间 [ l , r ] [l,r] [l,r],用前缀和求区间内的和,总时间复杂度为 O ( n 2 ) O(n^2) O(n2).

考虑优化.枚举每个区间 [ l , r ] [l,r] [l,r],其中的数的和为 p r e [ r ] − p r e [ l − 1 ] pre[r]-pre[l-1] pre[r]pre[l1],即在 0 ∼ r 0\sim r 0r的范围求有多少个 l   s . t .   ( p r e [ r ] − p r e [ l − 1 ] ) % k = 0 l\ s.t.\ (pre[r]-pre[l-1])\%k=0 l s.t. (pre[r]pre[l1])%k=0,亦即在 0 ∼ ( r − 1 ) 0\sim (r-1) 0(r1)的范围求有多少个 l   s . t .   ( p r e [ r ] − p r e [ l ] ) % k = 0 l\ s.t.\ (pre[r]-pre[l])\%k=0 l s.t. (pre[r]pre[l])%k=0,即有多少个 p r e [ l ] pre[l] pre[l] p r e [ r ] pre[r] pre[r] k k k的余数相等.用数组 c n t [ i ] cnt[i] cnt[i]表示余数为 i i i的数的个数.总时间复杂度 O ( n ) O(n) O(n).

代码
const int MAXN = 1e5 + 5;
int n, k;
ll pre[MAXN];
int cnt[MAXN];  // cnt[i]表示余数为i的数的个数

int main() {
	cin >> n >> k;
	for (int i = 1; i <= n; i++) {
		cin >> pre[i];
		pre[i] += pre[i - 1];
	}

	ll ans = 0;
	cnt[0] = 1;  // pre[0]=0,模k余0
	for (int i = 1; i <= n; i++) {
		ans += cnt[pre[i] % k];
		cnt[pre[i] % k]++;
	}
	cout << ans;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值