LGR-128】梦熊信奥联盟 1 月普及组月赛

P8938 A. 小狗哥哥

思路

  1. 由题意, p p p 需要满足:当 a i ≠ 1 a_i\ne1 ai=1 时, m i a i ≤ p ≤ m i ( a i − 1 ) \frac{m}{ia_i}\leq p\leq \frac{m}{i(a_i-1)} iaimpi(ai1)m;当 a i = 1 a_i=1 ai=1 时, m i a i ≤ p \frac{m}{ia_i}\leq p iaimp。所有的不等式做交集得到满足要求的 p p p
  2. 担心精度损失问题。注意到 p p p 一定是整数。故 p < 2.1 p<2.1 p<2.1 p < 3 p<3 p<3 是等价的; p ≥ 2.1 p\geq 2.1 p2.1 p ≥ 3 p\geq 3 p3 是等价的。故不需要使用浮点数,判断分子是否整除分母,然后分类处理即可。
  3. 时间复杂度为 O ( n ) \mathcal O(n) O(n)

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 100;
const int INF = 2147483647;
int n, m, a[maxn];
int ansi = -1, ansii = 2147483647;
int main ()
{
	scanf ("%d %d", &n, &m);
	for (int i = 1; i <= n; i++) 
	{
		int a;
		scanf ("%d", &a);
		if (m % (i * a) == 0) ansi = max (ansi, m / (i * a));
		else ansi = max (ansi, m / (i * a) + 1);
		if (a != 1) 
		{
			if (m % (i * (a - 1)) == 0) ansii = min (ansii, m / (i * (a - 1)));
			else ansii = min (ansii, m / (i * (a - 1)) + 1);
		}
	}
	if (ansii <= ansi) printf ("0"), exit (0);
	if (ansi == -1 || ansii == INF) printf ("xiaogougege"), exit (0);
	printf ("%d", ansii - ansi);
	return 0;
}

P8939 B. 去年 11 月卵梦蕾简易钨丝

前置知识之 set

  1. set 的定义:set 是一个内部自动有序且不含重复元素的容器,其实现自动去重并升序排序。使用 set 需要用到头文件:#include< set >。可以用迭代器 *it 去访问 set 中的元素。
  2. 常用函数及其功能
  3. 由于 set 内部的自动升序,因此插入、删除、查找为某个值的元素的时间复杂度均为 O ( log ⁡ n ) \mathcal O(\log n) O(logn)

思路

  1. 考虑如何判断元素是否在序列中。用 v i s i vis_i visi 记录 i i i 在目前的序列中出现的次数。若 v i s i = 0 vis_i=0 visi=0,则说明 i i i 不在序列中。
  2. 考虑构造 ∑ i = 1 n ∣ a i + 1 − a i ∣ \sum\limits_{i=1}^n|a_{i+1}-a_i| i=1nai+1ai 的最小值。首先我们对 a a a 进行不降序排序,得到 a 1 a 2 … a n a_1a_2\dots a_n a1a2an。由几何直观容易证明,此序列使得 ∑ i = 1 n ∣ a i + 1 − a i ∣ \sum\limits_{i=1}^n|a_{i+1}-a_i| i=1nai+1ai 最小,且最后的答案为 2 ( max ⁡ { a i } − min ⁡ { a i } ) 2(\max\{a_i\}-\min\{a_i\}) 2(max{ai}min{ai})在这里插入图片描述
  3. 考虑如何得到最大值与最小值。set 可以实现 O ( log ⁡ n ) \mathcal O(\log n) O(logn) 地插入元素与删除元素, O ( 1 ) \mathcal O(1) O(1) 地查询最大值与最小值。
  4. 时间复杂度为 O ( ( n + q ) log ⁡ n ) \mathcal O((n+q)\log n) O((n+q)logn)

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 100;
int n, q, vis[maxn];
set <int> s; 
int main ()
{
	scanf ("%d %d", &n, &q);
	for (int i = 1; i <= n; i++) 
	{
		int a;
		scanf ("%d", &a);
		vis[a] ++, s.insert (a);
 	}
	while (q --) 
	{
		int op, x;
		scanf ("%d %d", &op, &x);
		if (op == 1) 
		{
			if (!vis[x]) 
			{
				printf ("-1\n");
				continue;
			}
			if (!-- vis[x])
				s.erase (x);
			printf ("%d\n", 2 * (*--s.end () - *s.begin ()));
		}
		else 
		{
			if (!vis[x] ++)
				s.insert (x);
			printf ("%d\n", 2 * (*--s.end () - *s.begin ()));
		}
	}
	return 0;
}

收获

  1. 学习了 set

P8940 C. 不见故人

思路

  1. 思考最后相同的数是什么。首先,对于一个区间我们可以划分为若干个子区间,对子区间在进行上述操作,如此继续,则一定存在某个时刻,某个区间分为若干个后不可再分,这时我们按题目中的要求合并,最后得到这个相同的数一定是 gcd ⁡ { a 1 , a 2 , … a n } \gcd\{a_1,a_2,\dots a_n\} gcd{a1,a2,an},记为 G G G
  2. 思考如何得到最小代价。容易想到最初值为 G G G 的元素,在不影响答案的前提下,应当尽可能少地参与到操作中。于是,我们将最初值为 G G G 的元素标记为红色,形成若干红色区间。则记被红色区间分隔开的若干个区间为黑色区间,并记 p p p 为黑色区间的个数, l i , r i l_i,r_i li,ri 分别为第 i i i 个黑色区间的左端点与右端点。则接下来我们只需要通过讨论这些黑色区间得到最小代价。
  3. 思考如何通过黑色区间得到最小代价。当 p = 0 p=0 p=0 时,答案显然为 0 0 0。下面讨论 p > 0 p>0 p>0 的情形。易得, r i r_i ri 为有效讨论点,因为若把 1 1 1 j , j ∈ [ l i , r i ) j,j\in[l_i,r_i) j,j[li,ri) 变为 G G G,再把 j + 1 j+1 j+1 r i r_i ri 变为 G G G,相比于直接把 1 1 1 r i r_i ri 变成 G G G 会多消耗一次 k k k。故我们令 d p i dp_i dpi 表示把 1 1 1 r i r_i ri 的全部元素变为 G G G 付出的最小代价,则 d p p dp_p dpp 即为最后的答案。
  4. 思考 d p i dp_i dpi 的转移方程。若 d p i dp_i dpi 的转移没有跨过红色区间,则 d p i ← d p i − 1 + r i − l i + 1 + k + [ gcd ⁡ { a j ∣ j ∈ [ l i , r i ] } ≠ G ] dp_i\gets dp_{i-1}+r_i-l_i+1+k+[\gcd\{a_j|j\in[l_i,r_i]\}\ne G] dpidpi1+rili+1+k+[gcd{ajj[li,ri]}=G]。若 d p i dp_i dpi 的转移跨过了红色区间,则 d p i ← min ⁡ { d p j + r i − l j + 1 + 1 + k ∣ j ∈ [ 0 , i − 1 ) } dp_i\gets \min\{dp_j+r_i-l_{j+1}+1+k|j\in[0,i-1)\} dpimin{dpj+rilj+1+1+kj[0,i1)}。故我们只需要动态维护 d p j − l j + 1 , j ∈ [ 0 , i − 1 ) dp_j-l_{j+1},j\in[0,i-1) dpjlj+1,j[0,i1) 的最小值即可,用 set 可以很优秀的实现。
  5. 最坏时间复杂度为 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn)

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 4e6 + 100;
int n, k, a[maxn], l[maxn], r[maxn], p = 0, G, dp[maxn], last_dp;
int gcd (int a, int b)
{
	if (a < b) swap (a, b);
	if (b == 0) return a;
	else return gcd (b, a % b);
}  
set <int> s;
int main ()
{
	scanf ("%d %d", &n, &k);
	for (int i = 1; i <= n; i++) 
	{
		scanf ("%d", &a[i]);
		G = gcd (G, a[i]);
	}
	int ii = 1;
	// [l_i,r_i] 的获得有细节,需要注意。
	while (ii <= n) 
	{
		while (a[ii] == G && ii <= n) ii ++;
		l[++ p] = ii;
		// l[p] == n + 1 表明最后这一块全为 G
		if (l[p] == n + 1)
		{
			p --;
			break;
		}
		while (a[ii] != G && ii <= n) ii ++;
		r[p] = ii - 1;
	}
	if (p == 0) printf ("0"), exit (0);
	// 为了保证正确性,定义 dp_0 - l_1 存在。
	last_dp = -l[1];
	for (int i = 1; i <= p; i++) 
	{
		int Gi = 0;
		for (int j = l[i]; j <= r[i]; j++)
			Gi = gcd (Gi, a[j]);
		dp[i] = dp[i - 1] + r[i] - l[i] + 1 + k + (Gi == G ? 0 : 1);
		if (!s.empty ()) dp[i] = min (dp[i], r[i] + 1 + k + *s.begin ());
		s.insert (last_dp);
		last_dp = dp[i] - l[i + 1]; 
	}
	printf ("%d", dp[p]);
	return 0;
}

收获

  1. 觉得 dp 真的难想地绝绝子!
  2. 认识到观察最后序列的性质很重要。
  3. 认识到简化序列(红黑分段)的重要性。
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值