[Week 17] 每日一题(C++,模拟,数学)

[Daimayuan] T1 碰撞2(C++,模拟)

\(xOy\) 坐标系中有 \(N\) 个人,第 \(i\) 个人的位置是 \((X_i,Y_i)\),并且每个人的位置都不同。

我们有一个由 LR 组成的长为 \(N\) 的字符串 \(S\)\(S_i=\) R 代表第 \(i\) 个人面向右,\(S_i=\) L 代表第 \(i\) 个人面向左。

现在所有人开始朝着他们各自面向的方向走,即面向右 \(x\) 就增,面向左 \(x\) 就减。

例如,当 \((X_1,Y_1)=(2,3)\),\((X_2,Y_2)=(1,1)\),\((X_3,Y_3)=(4,1)\),\(S=\) RRL 时,人们的移动如图。

f33104f8bc05a920f2b74ead8ad1e3d2.png

我们把两个人对向行走到一个位置称为一次碰撞。请问如果人们可以无限走下去,会有人产生碰撞吗?

输入格式

第一行一个整数 \(N\)

接下来 \(N\) 行,每行两个整数 \(X_i\)\(Y_i\),表示第 \(i\) 个人的位置;

最后一行是一个由 LR 组成的长为 \(N\) 的字符串 \(S\)

输出格式

如果会有碰撞,输出 Yes,否则输出 No

样例输入 1

3
2 3
1 1
4 1
RRL

样例输出 1

Yes

样例输入 2

2
1 1
2 1
RR

样例输出 2

No

样例输入 3

10
1 3
1 4
0 0
0 2
0 4
3 1
2 4
4 2
4 4
3 3
RLRRRLRLRR

样例输出 3

Yes

数据规模

所有数据保证 \(2≤N≤2×10^5\)\(0≤X_i≤10^9\)\(0≤Y_i≤10^9\)

解题思路

根据题意,我们很容易就能得出碰撞条件:

(1)同一行中,即\(y\)相同

(2)行走方向相反

(3)向右走的人在左边,向左走的人在右边

那么我们的思路形成了:

遍历每一行,找出每一行中的\(min\{x|x为向右走的人的横坐标\}\)\(max\{y|y为向左走的人的横坐标\}\)

接下来的问题就是如何存储数据

显然,想要对应每一个\(y\)值开一个数组是不现实的

但是我们简单思考一下,发现也没必要为每一个\(y\)值开一个数组

采用sort算法,把相同\(y\)值的个体连在一起就可以了

最后,AC代码如下

#include <iostream>
#include <algorithm>
using namespace std;
const int max_n = 2e5;
const int max_x = 1e9;
const int max_y = 1e9;
const int NaN = 0x3F3F3F3F;

int n;
struct person { int x, y, dirction; }persons[max_n + 1];

int main() {
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> persons[i].x >> persons[i].y;
	}
	string str;
	cin >> str;
	for (int i = 0; i < n; i++) {
		if (str[i] == 'R') persons[i + 1].dirction = 0;
		else persons[i + 1].dirction = 1;
	}
	sort(persons + 1, persons + 1 + n, [](person p1, person p2) {
		return p1.y < p2.y;
		});
	int line = -1, l = NaN, r = -1;
	for (int i = 1; i <= n; i++) {
		if (persons[i].y == line) {
			if (persons[i].dirction) r = max(r, persons[i].x);
			else l = min(l, persons[i].x);
		}
		else {
			if (l < r) {
				cout << "Yes" << endl;
				return 0;
			}

			l = NaN; r = -1;
			line = persons[i].y;

			if (persons[i].dirction) r = max(r, persons[i].x);
			else l = min(l, persons[i].x);
		}
	}
	if (l < r) cout << "Yes" << endl;
	else cout << "No" << endl;
	return 0;
}

[Daimayuan] T2 优美!最长上升子序列(C++,DP)

多组数据。

每组将给定一个数组。派派希望从中选择一个递增的子序列,越长越好。

但派派认为,这样选出来的子序列依然不够「优美」,形式化的讲,派派希望选择的下标(从 \(1\) 开始)需要满足

\(i_1∣i_2∣i_3∣⋯∣i_k\)

其中 \(a|b\) 表示整除, 即 \(a\)\(b\) 的约数。

请你帮助派派完成任务吧!

注:子序列的含义不再赘述。

输入格式

第一行一个整数 \(T\),表示接下来有 \(T\) 组数据。

每组数据包含两行,第一行包含一个整数 \(N\)

随后一行,包含 \(N\) 个整数,表示原数组 \(\{A\}\)

输出格式

对于每组数据,输出一行,包含一个数,表示能选出的「优美」的最长上升子序列长度。

数据规模

  • \(1≤T≤100\)
  • \(1≤N≤10^6\),但保证 \(\sum_{i=1}^{T}{N_i≤10^6}\)
  • \(1≤A_i≤10^9\)

样例输入

4
4
1 4 6 7
2
2 2
10
1 2 3 4 5 6 7 8 9 10
10
10 9 8 6 5 2 3 1 2 1

样例输出

3
1
4
1

解释:

对于第一组数据,能选择的「优美」最长上升子序列为 \(\{A_1,A_2,A_4\}=\{1,4,7\}\)

对于第三组数组,选择 \(\{A_1,A_2,A_4,A_8\}=\{1,2,4,8\}\)

对于第四组数据,可选择的「优美」最长上升子序列长度为 \(1\)

解题思路

在寻找「优美」的最长上升子序列之前,我们先回顾一下最长上升子序列的寻找

for (int i = 1; i <= n; i++) {
	for (int j = 1; j < i; j++) {
		if (num[i] > num[j]) {
			pre[i] = max(pre[i], pre[j] + 1);
		}
	}
}

pre[i]中存储的是以对应num[i]结尾的最长上升子序列长度

要想寻找以num[i]结尾的最长上升子序列,我们只需要找到num[i]所能接上的最长前缀pre[j](\(1 \le j<i\))即可

如果想采用同样的思路,我们会遇到一个问题

因为题目要求索引可以逐个整除,但是我们很难找到所有符合要求的索引

虽然向前找很难,但是向后找很容易,例如,对于\(i\),符合条件的索引即为\(k*i(k \in Z)\)

所以我们更新一下算法

for (int i = 1; i <= n; i++) {
	for (int j = 2 * i; j <= n; j += i) {
		if (num[i] < num[j]) {
			pre[j] = max(pre[j], pre[i] + 1);
		}
	}
}

与常规动态规划的更新方式不同,我们不是根据过去的结果推导出当前的值,而是根据当前的结果去推导未来的值

这种方法可行的原理是:在到达pre[i]之前,我们已经尝试用所有符合条件的pre[j](\(1 \le j < i\))去更新pre[i]了,从结果来看,这与之前是一致的

然后计算一下时间复杂度\(O(Nlog_2N)\),可以接受

最后,AC代码如下

#include <iostream>
using namespace std;
const int max_t = 100;
const int max_n = 1e6;
const int max_a = 1e9;

int t, n, arr[max_n + 1];
int dp[max_n + 1];

int main() {
	cin >> t;
	for (int i = 0; i < t; i++) {
		cin >> n;
		for (int j = 1; j <= n; j++) {
			cin >> arr[j];
		}

		int ans = 1;
		for (int j = 1; j <= n; j++) dp[j] = 1;
		for (int j = 1; j <= n; j++) {
			int z;
			for (z = 2 * j; z <= n; z += j) {
				if (arr[z] > arr[j]) {
					dp[z] = max(dp[z], dp[j] + 1);
					ans = max(ans, dp[z]);
				}
			}
		}
		cout << ans << endl;
	}
	return 0;
}

[Daimayuan] T3 巨大的牛棚(C++,矩阵前缀和)

题目描述

农夫约翰想要在他的正方形农场上建造一座正方形大牛棚。他讨厌在他的农场中砍树,想找一个能够让他在空旷无树的地方修建牛棚的地方。我们假定,他的农场划分成 n * n的方格。输入数据中包括有树的方格的列表。你的任务是计算并输出,在他的农场中,不需要砍树却能够修建的最大正方形牛棚。牛棚的边必须和水平轴或者垂直轴平行。 考虑下面的方格,它表示农夫约翰的农场,.表示没有树的方格,#表示有树的方格

........
.#...#..
........
........
........
..#.....
........
........

那么最大的牛棚是5*5的。

输入描述

第一行输入一个正整数 \(n(1≤n≤1000)\)代表农场的大小,一个正整数\(T(1≤T≤n∗n)\), 接下来 \(T\) 行,每行\(2\)个整数,代表有树的格子的横纵坐标,保证任意两个树格子不相同

输出描述

输出一个正整数代表牛棚的最大边长

样例输入

8 3
2 2
2 6
6 3

样例输出

5

解题思路

可以看出问题具有单调性:牛棚越大,修建的可能性越小;反之,可能性越大

而我们需要找出可能修建的最大的牛棚,所以采用二分法

int bin_search() {
	int l = 0, r = w + 1, m;
	while (l + 1 != r) {
		m = (l + r) / 2;
		if (judge(m)) l = m;
		else r = m;
	}
	return l;
}

然后就是如何实现judge函数的问题了

显然,判断牛棚大小是否可行,需要遍历所有可能的位置,采用二重循环实现

bool judge(int m) {
	for (int i = 1; i <= w - m + 1; i++) {
        for (int j = 1; j <= w - m + 1; j++) {
            if (找到可行位置) return true;
        }
    }
    return false;
}

那么最后一个问题:找到可行位置的条件是什么?

如果一棵一棵树判断,肯定TLE的飞起,所以需要想一想其他方法

这时候就要提到矩阵前缀和的概念了,这一方法应用于矩阵中,用于降低判断的时间复杂度

规定\(pre[x][y]=\sum_{1 \le i \le x}{\sum_{1 \le j \le y}{map[i][j]}}\)

其中map为二维bool数组,有树为true,无树为false

接下来介绍另外一个原理:包含排斥原理

首先看一下下面这张图

image

我们要计算\(4\)号区域的\(sum_4\)则有\(sum_{4}=sum_{1,2,3,4}-sum_{1,2}-sum_{1,3}+sum_{1}\)

这就是包含排斥原理的简单理解,也足够我们解决这道题了

应用以上两个概念,我们可以计算出指定区域内的矩阵和,如果和为\(0\)则可行;反之,继续尝试下一个区域

最后,AC代码如下

#include <iostream>
using namespace std;
const int max_n = 1e3;

int w, n;
bool map[max_n + 1][max_n + 1];
int pre[max_n + 1][max_n + 1];

bool judge(int m) {
	for (int i = 1; i <= w - m + 1; i++) {
		for (int j = 1; j <= w - m + 1; j++) {
			int x = i + m - 1, y = j + m - 1;
			if (!(pre[x][y] - pre[x][j - 1] - pre[i - 1][y] + pre[i - 1][j - 1]))
				return true;
		}
	}
	return false;
}

int bin_search() {
	int l = 0, r = w + 1, m;
	while (l + 1 != r) {
		m = (l + r) / 2;
		if (judge(m)) l = m;
		else r = m;
	}
	return l;
}

int main() {
	cin >> w >> n;
	int x, y;
	for (int i = 1; i <= n; i++) {
		cin >> x >> y;
		map[x][y] = true;
	}
	for (int i = 1; i <= w; i++) {
		for (int j = 1; j <= w; j++) {
			pre[i][j] = map[i][j] + pre[i - 1][j] + pre[i][j - 1] - pre[i - 1][j - 1];
		}
	}
	cout << bin_search() << endl;
	return 0;
}


[Daimayuan] T4 高利贷(C++,二分)

\(19\)岁的大学生小 \(L\) 家里情况不好,每月固定生活费不足以买一部苹果手机。当他得知有贷款机构可以贷小额贷款并且可以分期等额还款,每月只需要还几百块时,在虚荣心的驱使下他开始自己的贷款之旅。

贷款机构声称利率按月累计,贷款人在贷款期间每月偿还固定的分期付款金额。

给出小 \(L\) 贷款的原值为 \(n\),分期付款金额 \(m\) 和分期付款还清贷款所需的总月数 \(k\),求该贷款的月利率 \(p\)

输入格式

三个用空格隔开的正整数 \(n\)\(m\)\(k\),含义如上述所示。

输出格式

一个实数 \(p\),表示该贷款的月利率(用小数表示),和标准输出绝对值不超过\(10^{−6}\)即可。

数据范围

\(1≤n,m≤10^6\),\(1≤k≤300\)

\(0≤p≤5\),

\(n≤m×k\)

输入样例1

1000 1200 1

输出样例1

0.200000

输入样例2

1000 100 12

输出样例2

0.029229

样例解释

对于第一组样例,小 \(L\) 贷款了 \(1000\) 元,并在一个月后支付 \(1200\) 元还清了贷款。因此计算月利率 \(p\) 的公式为 \(1000×(1+p)=1200\)\(\frac{1200}{(1+p)}=1000\),求出 \(p=0.200000\)

对于第二组样例,小 \(L\) 贷款了 \(1000\) 元,并以每月支付 \(100\) 元的方式在 \(12\) 个月后还清了贷款。由于月利率的存在,他第 \(k\) 个月偿还的金额只相当于 \(\frac{100}{(1+p)^k}\) 元的初始金额,且这\(12\)个月共偿还初始金额\(1000\)元,求出 \(p=0.029229\)

解题思路

首先重新理解一下题意:

我们一共需要还款\(n\)元,每个月的还款金额\(m\)是固定的,分\(k\)个月还清,题目要求月利率\(p\)

于是有\(n=\sum_{i=1}^{k}{\frac{m}{(1+p)^k}}\),直观的理解就是虽然我们每月还款金额固定,但所占初始金额的比重越来越小

根据这个计算公式,因为题目给出了\(m,k\),对于任意的\(p\),我们可以计算出相应\(n\)

如果计算出的\(n\)值更大,我们应该尝试把\(p\)上调;反之,把\(p\)下调

可以看出,二分搜索的算法与这一思路完全吻合

接下来实现代码

题目给出了\(p\)的搜索区间\([0,5]\),停止二分的条件就是精度达到要求,即把答案区间长度收缩到小于\(10^{-6}\)

double bin_search() {
	double l = 0.0, r = max_p, m;
	while (r - l > 1e-6) {
		m = (l + r) / 2;
		if (judge(m)) l = m;
		else r = m;
	}
	return l;
}

关于judge函数的实现,之前已经说过,这里直接展示代码

bool judge(double p) {
	double down = 1 + p;
	double sum = 0.0;
	for (int i = 0; i < k; i++) {
		sum += m / down;
		down *= 1 + p;
	}
	return sum - n > 0;
}

最后,AC代码如下

#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
const int max_n = 1e6;
const int max_m = 1e6;
const int max_k = 300;
const int max_p = 5;

int n, m, k;

bool judge(double p) {
	double down = 1 + p;
	double sum = 0.0;
	for (int i = 0; i < k; i++) {
		sum += m / down;
		down *= 1 + p;
	}
	return sum - n > 0;
}

double bin_search() {
	double l = 0.0, r = max_p, m;
	while (r - l > 1e-6) {
		m = (l + r) / 2;
		if (judge(m)) l = m;
		else r = m;
	}
	return l;
}

int main() {
	cin >> n >> m >> k;
	cout << setiosflags(ios::fixed) << setprecision(6) << bin_search() << endl;
	return 0;
}

[Daimayuan] T5 背包(C++,模拟)

\(cc\)有一个背包,背包的体积为\(w\),有\(n\)个物品,每一个物品的体积为\(a_i\)

\(cc\)希望将其中的一些物品放入他的背包中,他希望这些物品的体积之和至少是背包体积的一半,并且不超过背包的体积,即 \(⌈w/2⌉≤sum≤w\)

请你帮\(cc\)判断这些物品中有没有符合条件的物品组合,如果有输出"\(YES\)", 没有输出"\(NO\)"

\(cc\)至少会拿一个物品

输入格式

第一行给出测试样例个数\(T\)

对于每一个样例:

第一行给出一个\(n\)和一个\(w\)\(n\)个物品,背包的总体积是\(w\)

第二行给出一个序列\(a_1,...a_n\),代表每一个物品的体积

输出格式

如果有请输出"\(YES\)", 没有输出"\(NO\)"

数据范围

\(1≤t≤10^4\),\(1≤\sum{n}≤2*10^5\),\(1≤w≤10^{18}\),\(0≤w_i≤10^9\)

样例输入1

3
1 3
3
6 2
19 8 19 69 9 4
7 12
1 1 1 17 1 1 1

样例输出

YES
NO
YES

解题思路

学过背包问题的人不要被标题给误导了\(qwq\),因为本题背包容量过大根本不能动态规划

我们回归题意,重新出发

我们把物品分为三类:

(1)如果输入的物品体积大于背包容量,对我们来说就没有意义

(2)如果输入物品体积\(⌈w/2⌉≤w_i≤w\),我们就可以输出\(YES\)

(3)如果输出物品体积\(w_i<⌈w/2⌉\),我们就累计这类物品的总体积,如果最终总体积大于\(⌈w/2⌉\),我们就可以输出\(YES\)

对于第三类,不必担心物品总体积会直接超过\(w\),因为我们可以保证\(w_i<⌈w/2⌉\),即\(w_i \le w/2\)

所以我们就可以在常数的时间复杂度下解决本题~

最后,AC代码如下

#include <iostream>
using namespace std;
const int max_t = 1e4;
const int max_n = 2e5;
const long long max_w = 1e18;
const int max_item = 1e9;

long long t, n, w;

int main() {
	cin >> t;
	for (int i = 0; i < t; i++) {
		cin >> n >> w;
		long long item, sum = 0, ans = 0;
		for (int i = 0; i < n; i++) {
			cin >> item;
			if (item <= w) {
				if (item >= (w + 1) / 2) ans = 1;
				else {
					sum += item;
					if (sum >= (w + 1) / 2) ans = 1;
				}
			}
		}
		if (ans) cout << "YES" << endl;
		else cout << "NO" << endl;
	}
	return 0;
}

[Daimayuan] T6 三回文序列(C++,前缀和)

给定一个长度为\(n\)的序列\(a\)

我们定义三回文序列是形如\(\underbrace{a...a}_{k_1}\underbrace{b...b}_{k_2}\underbrace{a...a}_{k_1}\)的序列,例如:\([1,1,1,2,2,1,1,1]\)是一个三回文序列,而\([1,1,1,2,2,2,3,3,3],[1,1,2,2,1]\)都不是三回文序列。

现在,希望你找到序列\(a\)中最长的三回文序列的长度。

注意,\(k_1,k_2\)可以为\(0\)

输入格式

第一行给出一个数字\(T\),表示\(T\)组测试用例

对于每一个测试用例

第一行给出序列的长度\(n\)

第二行给出序列\(a_1,a_2,a_3...a_n\)

输出格式

对于每一个测试用例,输出最长的三回文序列的长度。

数据范围

\(1≤t≤2000\)

\(1≤\sum{n}≤2*10^5\),\(1≤a_i≤26\)

样例输入

6
8
1 1 2 2 3 2 1 1
3
1 3 3
4
1 10 10 1
1
26
2
2 1
3
1 1 1

样例输出

7
2
4
1
1
3

解题思路

通过样例,我们注意到三回文序列不一定是连续序列

也就是说1 1 2 2 3 2 1 1的最长三回文序列是1 1 2 2 2 1 1

要想找出最长的三回文序列,我们就需要找出所有三回文序列

首先想到动态规划,但是很难找出最优子结构

然后因为是序列问题,我们想到了前缀和

基本思路:尝试\(1\)$26$的每一种作为$a$,然后尝试所有可能的$k_1$,然后再尝试$1$\(26\)的每一种作为\(b\),每一种\(b\)会对应一个\(k_2\)

这么直接看可能不太容易懂,可以暂时理解一下大概

由这个思路很容易看出我们要采用三重循环来实现

for (int a = 1; a <= 26; a++) {
	for (int k1 = 0; k1 <= a的总数; k1++) {
		for (int b = 1; b <= 26; b++) {
			...
		}
	}
}

对应于每一个\(a\)\(k_1\)的组合(\(i\)\(j\)),我们想要知道左右两个索引,以便定下来\(b\)序列所在的区间

for (int a = 1; a <= 26; a++) {
	for (int k1 = 0; k1 <= a的总数; k1++) {
		int l = 左索引, r = 右索引;
		if (l > r) break;
		for (int b = 1; b <= 26; b++) {
			int k2 = [l+1, r-1]范围内b的数量;
			max_len = max(max_len, k1 * 2 + k2);
		}
	}
}

好了,我们的伪代码写完了,接下来是如何把对应部分替换为真正的代码

可以看出,我们有两个要求:

(1)指定\(a\)的数量\(k_1\),我们要求返回它的索引\(l,r\)

(2)指定某个区间范围,我们要求返回该范围内指定元素\(b\)的数量

实现方式是维护两个数组sum[26][n], indices[26][n]

sum[i][j]存储的前j个元素的i的数量

indices[i][j]存储的是i的前缀和为j时在序列中的下标

如何使用这两个数组呢?

for (int a = 1; a <= 26; a++) {
	for (int k1 = 0; k1 <= sum[a][n]; k1++) {
		int l, r;
        if (k1) {
        	l = indices[a][k1];
            r = indices[a][sum[a][n] - k1 + 1];
		}
        else {
            l = 0;
            r = n + 1;
        }
        
		if (l > r) break;
        else if (l == r) max_len = max(max_len, 2 * k1 - 1);
		else {
            for (int b = 1; b <= 26; b++) {
                int k2 = sum[b][r - 1] - sum[b][l - 1];
                max_len = max(max_len, k1 * 2 + k2);
            }
        }
	}
}

最后,AC代码如下

#include <iostream>
using namespace std;
const int max_num = 26;
const int max_len = 2e5;

int sum[max_num + 1][max_len + 1], indices[max_num + 1][max_len + 1];

int main() {
	int t, n, x;
	cin >> t;
	for (int i = 0; i < t; i++) {
		cin >> n;
		for (int j = 1; j <= n; j++) {
			cin >> x;
			for (int k = 1; k <= max_num; k++) {
				sum[k][j] = sum[k][j - 1];
			}
			sum[x][j]++;
			indices[x][sum[x][j]] = j;
		}

		int ans = 0;
		for (int a = 1; a <= 26; a++) {
			for (int k1 = 0; k1 <= sum[a][n]; k1++) {
				int l, r;
				if (k1) {
					l = indices[a][k1];
					r = indices[a][sum[a][n] - k1 + 1];
				}
				else {
					l = 0;
					r = n + 1;
				}

				if (l > r) break;
				else if (l == r) ans = max(ans, 2 * k1 - 1);
				else {
					for (int b = 1; b <= 26; b++) {
						int k2 = sum[b][r - 1] - sum[b][l];
						ans = max(ans, k1 * 2 + k2);
					}
				}
			}
		}
		cout << ans << endl;
	}
	return 0;
}

[Daimayuan] T7 简单的异或问题(C++,数学)

有一组整数 \(\{0,1,2,…,2_{m−1}\}\), 请从中选出 \(k\) 个数,使得这 \(k\) 个数的异或和为 \(n\), 请输出最大的满足条件的 \(k\)

输入格式

两个数 \(n\)\(m\), 其中 \(0≤n≤2m−1,1≤m≤60\)

输出格式

输出最大的满足条件的 \(k\)

样例输入

2 2

样例输出

3

样例解释

对于样例,我们可以选择 \({0,1,3}\)

解题思路

首先介绍一下异或和:

异或操作:同则为假,不同为真

1^0=1
0^1=1
0^0=0
1^1=0

给定两个数,这里以\(8\)\(4\)为例,其异或和

0000 1000
0000 0100
---------
0000 1100

接下来是解题思路:

这里先列出来一部分二进制格式的数字

0000 0
0001 1
0010 2
0011 3
0100 4
0101 5
0110 6
0111 7
1000 8
1001 9
1010 10
1011 11
1100 12
1101 13
1110 14
1111 15

然后我们可以发现:任意数量大于\(1\)\(0\)与相同数量的\(1\)作异或操作,无论顺序,结果均为\(0\)

再进一步看到:当\(m>1\)时,\(0\)~\(2^m-1\)之内的所有数的异或和为\(0\)

那么当\(m>1\)时,我们想得到任意数字\(n \in [0, 2^m-1]\)\(n \ne 0\),只需要从异或和中去掉\(n\)即可

\(m=1\)时,我们进行特殊判定即可

AC代码如下

#include <iostream>
using namespace std;

int main() {
	long long n, m;
	cin >> n >> m;
	if (m == 1) {
		if (n) cout << 2 << endl;
		else cout << 1 << endl;
	}
	else {
		if (!n) cout << (1LL << m) << endl;
		else cout << ((1LL << m) - 1) << endl;
	}
	return 0;
}

[Daimayuan] T8 子串的循环挪动(C++,模拟)

给出一个字符串 \(s\),你需要执行 \(m\) 个任务。每个任务给出两个下标 \(l_i,r_i\) 和一个整数 \(k_i\)(字符串的下标从 \(1\) 开始),表示你需要循环挪动 \(s\) 的子串 \(s[l_i...r_i]\ k_i\) 次。请从前到后依次执行给出的每个任务。

字符串的循环挪动操作:将最后一个字符移到第一个字符的位置,并且将其他所有字符向右移一个位置。

比如:如果字符串 \(s\)abacaba,一个任务为 \(l_1=3,r_1=6,k_1=1\),那么答案为 abbacaa。接下来一个任务为 \(l_2=1,r_2=4,k_2=2\),那么我们会得到 baabcaa

输入格式

第一行一个字符串 \(s\),该字符串只包含小写英文字符。

第二行一个整数 \(m\),表示任务个数。

接下来 \(m\) 行每行有三个整数 \(l_i,r_i\)\(k_i\)

输出格式

输出执行了 \(m\) 个任务后的最终的字符串 \(s\)

样例输入

abacaba
2
3 6 1
1 4 2

样例输出

baabcaa

数据规模

对于所有数据保证,\(1≤|s|≤10000\)\(|s|\) 表示字符串 \(s\) 的长度),\(1≤m≤300\)\(1≤l_i≤r_i≤|s|\)\(1≤k_i≤1000000\)

解题思路:

步骤如下:

(1)对\(k=k\%len\)(因为循环移动\(len = r - l + 1\)次之后,子串与操作前一致)

(2)取出子串尾部\(k\)个字符缓存

(3)将前\(len - k\)个字符向后移动\(k\)

(3)将缓存的字符放回子串首部

\(ps\):本来想先用模拟尝试深入理解一下题目,看一眼数据规模发现好像能过,结果就过了\(qwq\)

AC代码如下

#include <iostream>
using namespace std;
const int max_len = 1e4;

char str[max_len + 1];
char buffer[max_len + 1];

int main() {
	char c = '\0';
	int len = 0;
	while ((c = getchar()) != '\n' && c != '\r') {
		str[len++] = c;
	}
	str[len] = '\0';

	int t, l, r, k;
	cin >> t;
	for (int i = 0; i < t; i++) {
		cin >> l >> r >> k;
		k %= r - l + 1; l--; r--;
		for (int j = r - k + 1; j <= r; j++) {
			buffer[j] = str[j];
		}
		for (int j = r - k; j >= l; j--) {
			str[j + k] = str[j];
		}
		for (int j = r - k + 1, idx = l; j <= r; j++, idx++) {
			str[idx] = buffer[j];
		}
	}
	cout << str << endl;
	return 0;
}

[Daimayuan] T9 弗拉德和糖果 II(C++,数学)

不久前,弗拉德过生日,他收到了一包糖果。有 \(n\) 种糖果,第 \(i\) 种糖果有 \(a_i\) 个(\(1≤i≤n\))。

弗拉德决定每次只吃一个糖果。为了从吃东西中获得最大的乐趣,弗拉德不想连续吃两个相同类型的糖果。

帮助他弄清楚他是否可以在不连续吃两个相同的糖果的情况下吃掉所有的糖果。

简而言之,给定 \(n\) 个正整数 \(a_i\)\(a_i\) 表示有 \(a_i\)\(i\),找到是否存在一种序列,使得所有的数都用上,且不存在 \(i\) 连续的情况

输入格式:

第一行,包含一个整数 \(n\)。 第二行,包含 \(n\) 个正整数。

输出格式:

输出一行,如果存在,输出YES,否则输出NO

样例输入

2
1 1

样例输出

YES

说明

只有两种情况:

1 2
2 1

无论先吃哪种糖果,都能吃完且不连续吃相同类型的糖果

数据限制

对于 \(100\%\) 的数据,保证 \(1≤n≤5000000,1≤a_i≤2^{30}\)

解题思路:

因为要求不能连续吃相同的糖果,所以我们预先把相同糖果间留出空隙

我们只关心糖果之间的空隙能否被填充,并不关心之前或者之后有没有其他糖果,也不关心空隙之间到底有多少颗糖果

那么假设在吃\(a_i\)糖果之前,已经排好了\(sum\)颗糖果

(1)如果有\(sum \ge a_i - 1\),则可以顺利吃掉\(a_i\)糖果(空隙可以填充),同时更新\(sum\),回到(1)

(2)但是如果条件不成立,那么我们就会剩下\(a_i-sum-1\)颗糖果等待被吃

(3)然后我们接收下一个输入\(a_{i+1}\),如果\(a_{i+1}\)大于剩余糖果数量,则可以顺利吃掉\(a_i\)糖果;反之更新剩余糖果数量和\(sum\)后回到(3)等待下一次输入

(4)在顺利吃掉\(a_i\)糖果之后,要判断\(a_{i+1}\)是否能被吃掉,能的话回到(1),反之回到(3)

注:当sum > max_a的时候,无论接下来的输入糖果数量是多少,我们都可以吃掉,所以不用继续累计\(sum\),也就不用担心爆精度问题

AC代码如下

#include <stdio.h>
#include <stdlib.h>
const int max_n = 5e6;
const long long max_a = 2ll << 30;

int main() {
	int n;
	scanf("%d", &n);
	long long a, sum = 0, buffer = 0;
	for (int i = 0; i < n; i++) {
		scanf("%lld", &a);
		if (sum <= max_a) {
			if (!buffer) {
				if (sum >= a - 1) sum += a;//(1)
				else {//(2)
					sum = sum * 2 + 1;
					buffer = a - sum - 1;
				}
			}
			else {//(3)
				if (a >= buffer) {
					sum += buffer;
					if (sum >= a - 1) {
						sum += a;
                        buffer = 0;//return (1)
					}
					else {
						sum = sum * 2 + 1;
						buffer = a - sum - 1;//return (3)
					}
				}
				else {
					buffer -= a;
					sum += 2 * a;
				}
			}
		}
	}
	if (sum > max_a || !buffer) printf("YES");
	else printf("NO");
	return 0;
}

[Daimayuan] T10 上帝的集合(C++,线性表)

题目描述

现在上帝有一个空集合,现在他命令你为他执行下列三种操作 \(n\) 次,他每次会给你一个操作类型 \(op\)

操作\(1\):向集合中插入一个整数 \(x\);

操作\(2\):将集合中所有的数加上 \(x\);

操作\(3\):输出集合中最小的数,并从集合中将他删除,如果存在多个最小的整数,任意选择一个即可;

输入描述

第一行输入一个整数 \(n\)

接下来的 \(n\) 行,每行的输入如下所示。第一个数代表 \(op\),如果 \(op=1\)\(op=2\),第二个数代表 \(x_i\)

\(1\) \(x_i\)

\(2\) \(x_i\)

\(3\)

输出描述

如果 \(op=3\),请输出集合中的最小值。

样例输入

7
1 2
1 1
3
1 3
2 5
3
3

样例输出

1
7
8

数据范围

\(2≤n≤10^6\), \(1≤x_i≤10^{12}\)

解题思路:

采用优先队列维护集合

对于集合的更新操作,维护一个变量add,每次插入前将元素先减去add,每次输出前先将元素加上add后输出即可

AC代码如下

#include <stdio.h>
#include <stdlib.h>
#include <queue>
using namespace std;

priority_queue<long long, vector<long long>, greater<long long>>q;

int main() {
	int n;
	scanf("%d", &n);

	long long choice, x, add = 0;
	for (int i = 0; i < n; i++) {
		scanf("%lld", &choice);
		switch (choice) {
		case 1:
			scanf("%lld", &x);
			q.push(x - add);
			break;
		case 2:
			scanf("%lld", &x);
			add += x;
			break;
		case 3:
			printf("%lld\n", q.top() + add);
			q.pop();
			break;
		}
	}
	return 0;
}

posted @ 2023-04-16 20:02  WitheredSakura  阅读(27)  评论(0编辑  收藏  举报