This commit is contained in:
HuangHai
2025-08-30 18:35:01 +08:00
commit 1f397eca87
6258 changed files with 1487241 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 10010;
int n, m;
int a[N];
/*
5
3
1 2 3 4 5
答案
1 2 4 5 3
变化过程
(1) 1 2 3 5 4
(2) 1 2 4 3 5
(3) 1 2 4 5 3
*/
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i];
while (m--) next_permutation(a + 1, a + 1 + n); // 输入数据保证这个结果不会超出火星人手指能表示的范围。
/*
int a[4] = {1, 2, 3, 4};
sort(a, a + 4);
do {
for (int i = 0; i < 4; i++) cout << a[i] << " ";
cout << endl;
} while (next_permutation(a, a + 4));
*/
for (int i = 1; i <= n; i++) printf("%d ", a[i]);
return 0;
}

View File

@@ -0,0 +1,79 @@
## [$AcWing$ $420$. 火星人](https://www.acwing.com/problem/content/description/422/)  
### 1.算法:$STL$
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 10010;
int n, m;
int a[N];
/*
5
3
1 2 3 4 5
答案
1 2 4 5 3
变化过程
(1) 1 2 3 5 4
(2) 1 2 4 3 5
(3) 1 2 4 5 3
*/
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i];
while (m--) next_permutation(a + 1, a + 1 + n); // 输入数据保证这个结果不会超出火星人手指能表示的范围。
/*
int a[4] = {1, 2, 3, 4};
sort(a, a + 4);
do {
for (int i = 0; i < 4; i++) cout << a[i] << " ";
cout << endl;
} while (next_permutation(a, a + 4));
*/
for (int i = 1; i <= n; i++) printf("%d ", a[i]);
return 0;
}
```
### 2 、算法:利用贪心法解决字典序最小相关问题:
![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/{year}/{month}/{md5}.{extName}/202309181044220.png)
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 10010;
int q[N];
int n, m;
int main() {
cin >> n >> m;
for (int i = 0; i <= n; i++) cin >> q[i];
while (m--) { // 执行 m次
int k = n - 1; // 从后向前来
/*
Q:123654321 问下一个是啥?
算法步骤:
1、从向向前找第一个下降点,本例就是6
*/
while (q[k - 1] > q[k]) k--;
// 2、 k--:找到3
int t = k--;
// 3、从3开始,向后找第一个大于3的数字,就是4
while (t + 1 < n && q[t + 1] > q[k]) t++;
// 4、交换3和4
swap(q[t], q[k]);
// 5、对于折点的后半部进行翻转,即 124 653321 =>翻转=> 124 123356
reverse(q + k + 1, q + n);
}
for (int i = 0; i < n; i++) printf("%d ", q[i]);
return 0;
}
```

View File

@@ -0,0 +1,30 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 10010;
int q[N];
int n, m;
int main() {
cin >> n >> m;
for (int i = 0; i <= n; i++) cin >> q[i];
while (m--) { // 执行 m次
int k = n - 1; // 从后向前来
/*
Q:123654321 问下一个是啥?
算法步骤:
1、从向向前找第一个下降点,本例就是6
*/
while (q[k - 1] > q[k]) k--;
// 2、 k--:找到3
int t = k--;
// 3、从3开始,向后找第一个大于3的数字,就是4
while (t + 1 < n && q[t + 1] > q[k]) t++;
// 4、交换3和4
swap(q[t], q[k]);
// 5、对于折点的后半部进行翻转,即 124 653321 =>翻转=> 124 123356
reverse(q + k + 1, q + n);
}
for (int i = 0; i < n; i++) printf("%d ", q[i]);
return 0;
}

View File

@@ -0,0 +1,11 @@
#include <bits/stdc++.h>
using namespace std;
int a[] = {1, 4, 6, 5, 3, 2, 1};
int main() {
next_permutation(a, a + 7);
for (int i = 0; i < 7; i++)
cout << a[i] << " ";
// 答案:1 5 1 2 3 4 6
return 0;
}

View File

@@ -0,0 +1,23 @@
#include <bits/stdc++.h>
using namespace std;
int main() {
int a, b, n; // 2 ~10
string s;
cin >> a >> b >> n >> s;
a--, b--;
for (int i = 0; i < 5; i++) { // 跑5次
int k = n - 1; // 字符串的最后一位,k是位置
while (k >= 0 && b - (s[k] - 'a') < n - k) k--;
if (k < 0) break;
int c = s[k] + 1; // 长大1个
for (int j = k; j < n; j++)
s[j] = c++;
cout << s << endl;
}
/
return 0;
}

View File

@@ -0,0 +1,26 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 30010;
int n, m;
int w[N];
int main() {
// m:每组纪念品价格之和的上限
// n:购来的纪念品的总件数
cin >> m >> n;
for (int i = 0; i < n; i++) cin >> w[i]; // 表示所对应纪念品的价格
// 将价格由小到大排序
sort(w, w + n);
int res = 0;
// 双指针对撞
for (int i = 0, j = n - 1; i <= j; j--) {
if (w[i] + w[j] <= m) i++;
res++;
}
cout << res << endl;
return 0;
}

View File

@@ -0,0 +1,53 @@
## [$AcWing$ $430$. 纪念品分组](https://www.acwing.com/problem/content/description/432/)
(贪心,排序) $O(nlogn)$
直觉上讲,分组的时候应该尽可能让每一组的价值之和大一些。
由此得到如下算法:
将所有物品按价值排序;
从小到大枚举每个物品,每次给当前物品找一个价值尽可能大的且总价值没有超过上限的“同伴物品”,将两个物品分在一组,这一步可以使用双指针算法优化到 $O(n)$。
这样求出的组数就是最小值。
下面给出证明。这里可以使用 **调整法**:
假设最优解的分组方式和由上述算法得到的分组方式不同。那么我们考虑从后往前第一个分组不同的数,记为 $a$,假设由上述算法得到的“同伴物品”是 $b$,那么:
如果在最优解中,$a$ 单独一组,那么可以直接将 $b$ 从原组中取出,和 $a$ 放在一起,这样并没有增加组数;
如果在最优解中,$a$ 和 $c$ 放在一起,由上述算法可知,$c≤b$,那么我们可以将 $b$ 和 $c$ 所在位置交换,交换后两组的价值之和都没有超过上限,且这样也没有增加组数。
因此通过上述调整,我们可以在不增加组数的情况下,将最优解的分组方式调整成和上述算法相同,且这样的调整方式可以一直进行下去,直到两个方案相同为止。
因此我们可以在不增加组数的情况下,将最优解的方案调整成上述算法得到的方案,因此上述算法可以得到最优解。
时间复杂度
排序是算法瓶颈,因此时间复杂度是 $O(nlogn)$。
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 30010;
int n, m;
int w[N];
int main() {
// m:每组纪念品价格之和的上限
// n:购来的纪念品的总件数
cin >> m >> n;
for (int i = 0; i < n; i++) cin >> w[i]; // 表示所对应纪念品的价格
// 将价格由小到大排序
sort(w, w + n);
int res = 0;
// 双指针对撞
for (int i = 0, j = n - 1; i <= j; j--) {
if (w[i] + w[j] <= m) i++;
res++;
}
cout << res << endl;
return 0;
}
```

View File

@@ -0,0 +1,56 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int m, n, k, l, d; // m行n列,k条横向的通道,l条纵向的通道,d对同学交头接耳
int x[N], y[N]; // 横纵坐标数组
int b1[N], b2[N]; // 桶排要用的数组
int main() {
cin >> m >> n >> k >> l >> d;
while (d--) {
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
if (x1 == x2) // 横坐标相等,则记录到个数数组中
y[min(y1, y2)]++; // 表示隔开这两排的价值
else
x[min(x1, x2)]++; // 记得取min,即过道与前一个坐标保持一致
}
for (int i = 1; i <= k; i++) { // 循环k次
int mx = 0; // 临时最大值
int p; // 临时最大值位置
for (int j = 1; j <= m; j++) {
if (x[j] > mx) {
mx = x[j];
p = j;
}
}
x[p] = 0; // 求出max之后一定要记得清零!!否则无论排多少次都是一个答案
b1[p]++; // p这行是需要加入过道的
}
for (int i = 1; i <= l; i++) {
int mx = 0;
int p;
for (int j = 1; j <= n; j++) {
if (y[j] > mx) {
mx = y[j];
p = j;
}
}
y[p] = 0; // 同上
b2[p]++; // p这列是需要加入过道的
}
// 输出答案
for (int i = 1; i <= m; i++)
if (b1[i]) printf("%d ", i); // 表示需要隔开这行
puts("");
for (int i = 1; i <= n; i++)
if (b2[i]) printf("%d ", i);
return 0;
}

View File

@@ -0,0 +1,48 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int sum[N], g[N], h[N];
struct Node {
int b; // 距离的疲劳值
int s; // 推销的疲劳值
} a[N];
bool cmp(Node &X, const Node &Y) {
return X.s > Y.s; // 按推销疲劳值从大到小排序
}
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i].b; // 距离的疲劳值
for (int i = 1; i <= n; i++) cin >> a[i].s; // 推销的疲劳值
sort(a + 1, a + n + 1, cmp);
for (int i = 1; i <= n; i++) {
sum[i] += sum[i - 1] + a[i].s; // 前缀和处理 算出推销疲劳值最大的前i个人的疲劳值总和
g[i] = max(g[i - 1], 2 * a[i].b); // 求出前i个人的最大距离
}
for (int i = n; i >= 1; i--) {
h[i] = max(h[i + 1], 2 * a[i].b + a[i].s);
// 求出 后i个人的距离推销综合最大值
// 因为舍去了推销疲劳最小的人来让总大
// 所以这里跟q[n]求法不一样
}
/*
黄海点评:
1、这里的h[i]挺有意思的,它是指,从i~n中贡献值最大是多少,注意是从i开始,1~i-1的不在范围内讨论
2、i~n中的房子,从距离上来讲,可能是出现在sum[i-1]前面的,但这并不影响,
因为即使遍历到了这样的房子,max也不会变大,也就是此题精妙的地方。
*/
for (int i = 1; i <= n; i++) {
int res = max(sum[i] + g[i], sum[i - 1] + h[i]);
// 前n个人加上了前n个的最大距离疲劳
// 后舍去后的则不加 ,而是加上后n个人的综合最大 ,
cout << res << endl;
}
return 0;
}

View File

@@ -0,0 +1,115 @@
## [$AcWing$ $464$. 推销员](https://www.acwing.com/problem/content/description/466/)
### 一、理解测试用例
```cpp {.line-numbers}
距离疲劳值
s[i]: 1 2 3 4 5
推销疲劳值
a[i]: 1 2 3 4 5
```
① 取$1$个:$5*2+5=15$
② 取$2$个:$(5+4)+2*5=19$
③ 取$3$个:$(5+4+5)+2*5=22$
④ 取$4$个:$(2+3+4+5)+2*5=24$
⑤ 取$5$个:$(1+2+3+4+5)+2*5=25$
### 二、解题思路
距离是真的烦人,那就先不管它吧。因为想要疲劳值最大,那么先 **按照疲劳值从大到小排序** 一遍。
```cpp
sort(a + 1, a + 1 + n,cmp);
//其中cmp为从大到小排序
```
> **$Q$:什么是$X$?**
> 答:向 $X$ 家住户推销产品
此时,可以判断出,最大值 **可能** 为:对于每个$X$,只要加上前$X$大的疲劳值,再加上 **这些数中距离最远** 的并 **乘以$2$**,也就是:$$\sum_{i=1}^{X}(a[i])+s[j]∗2$$
其中,$s[j]$表示前 $X$ 个距离最远的。
**但是**,推销员可以通过 **走远一点**,虽然疲劳值比前面小,但是有可能把路程一算,反而更大。
因此,可以把$a[]$,即疲劳值中,**前$X$大中的最小值,即第$X$大的那一家舍去**,看看能不能通过走更远来换取更大的疲劳值总和。
而从后面看,**一定也会存在一个最大值**,把这个最大值和前面$X−1$个疲劳值总和加起来,看看会不会比前面第一种的情况大。
#### 证明:只需舍去最小值来走更远,无需舍去更多数来走更远。
其实这也不是一个证明..
因为已经从大到小排序了,所以,如果舍去$2$个疲劳值,那么后面只会在加上两个更小的疲劳值,以及两个之间最大的距离并乘$2$,那么这样还不如只舍去一个。
$2$个不行,那么$2$个以上更是不行了。
#### **举栗子**
硬讲太累了,还是用一个例子吧。
```cpp {.line-numbers}
s[i]:1,3,4,5,11
a[i]:5,4,2,1,1
```
- 若$X==1$:
- 直接取,值为:$1×2+5=7$
- 如果舍去当前(**最小**)的往后跑,值为:$11×2+1=23$
所以,最大值就为$max(7,23)=23$。
- 若$X==2$:
- 直接取,值为:$3×2+5+4=15$
- 把其中 **最小** 值舍去,也就是$4$去掉,往后跑值为:$11×2+1+5=28$,那么最大值为$max(15,28)=28$。
- 若把次小疲劳值,即$5$,也往后跑,那么值为$11×2+1=23$,可以发现,距离并未因此改变,仍为$11$,而位置移后,疲劳值只会减少,故只能移动最小疲劳值。
那么,最后一个问题,酱紫复杂度是会炸掉的,原因分析:
我们需要哪些信息呢?
<font color='red' size=4><b>
① $sum[i]:a[i]$数组前$i$位的前缀和,比如上面的$5+4$
② 除了计算疲劳值的前缀和,还需要一个前$i$个住房中,找到最大的距离值,这是题目中要求的数据,它能不能预处理出来呢?答案是可以的:从$i \sim n$一路$max$ $pk$不就记录下来了吗~
我们用$g[i]$来记录这个信息。
③ 把前$i$位中第$i$位去掉,保留前$i-1$位,那么,需要知道后面这一堆住房中距离的最大值。
很明显,这里在倒序由后向前进行$PK$记录即可。这里有一个坑点,就是如果你决策了选择某个住房,那么这个距离的最大值你肯定是妥妥的享用了,但不要忘记还要加这住房的疲劳值$a[i].b$
</b></font>
这样子预处理,就可以实现$O(nlogn)$啦 $qaq$!
**最终二选一就可以了~**
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int n; // n个客户
int sum[N], g[N], h[N]; // 疲劳值前缀和 前i个最大值 后i个最大值
struct Node {
int s; // 距离
int b; // 疲劳
const bool operator<(const Node &W) {
return b > W.b;
}
} a[N];
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i].s; // 住户i到入口的距离
for (int i = 1; i <= n; i++) cin >> a[i].b; // 向i住户推销产品会积累的疲劳值
sort(a + 1, a + 1 + n); // 按疲劳由大到小排序
for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + a[i].b; // 预算出疲劳值的前缀和
for (int i = 1; i <= n; i++) g[i] = max(g[i - 1], 2 * a[i].s); // 前i个距离最大值
for (int i = n; i >= 1; i--) h[i] = max(h[i + 1], 2 * a[i].s + a[i].b); // 后i个距离最大值
for (int i = 1; i <= n; i++) printf("%d\n", max(sum[i] + g[i], sum[i - 1] + h[i]));
return 0;
}
```

View File

@@ -0,0 +1,42 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
struct Node {
int s, b;
bool operator<(const Node &W) const {
return b > W.b; // 按疲劳值降序
}
} a[N];
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i].s;
for (int i = 1; i <= n; i++) cin >> a[i].b;
sort(a + 1, a + 1 + n);
for (int x = 1; x <= n; x++) {
int max_fatigue = 0;
int max_dist = 0;
int total_b = 0;
// 情况1:选择前x-1个疲劳值最大的,加上第x个的距离*2+疲劳值
for (int i = 1; i <= x; i++) {
if (i < x) {
total_b += a[i].b;
if (2 * a[i].s > max_dist) {
max_dist = 2 * a[i].s;
}
}
int current = total_b + max(2 * a[i].s + a[i].b, max_dist);
if (current > max_fatigue) {
max_fatigue = current;
}
}
cout << max_fatigue << endl;
}
return 0;
}

View File

@@ -0,0 +1,56 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int m, n, k, l, d; // m行n列,k条横向的通道,l条纵向的通道,d对同学交头接耳
int y[N], x[N]; // 横纵坐标数组
int b1[N], b2[N]; // 桶排要用的数组
int main() {
cin >> m >> n >> k >> l >> d;
while (d--) {
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
if (x1 == x2) // 横坐标相等,则记录到个数数组中
y[min(y1, y2)]++; // 表示隔开这两排的价值
else
x[min(x1, x2)]++; // 记得取min,即过道与前一个坐标保持一致
}
for (int i = 1; i <= k; i++) { // 循环k次
int mx = 0; // 临时最大值
int p; // 临时最大值位置
for (int j = 1; j <= m; j++) {
if (x[j] > mx) {
mx = x[j];
p = j;
}
}
x[p] = 0; // 求出max之后一定要记得清零!!否则无论排多少次都是一个答案
b1[p]++; // p这行是需要加入过道的
}
for (int i = 1; i <= l; i++) {
int mx = 0;
int p;
for (int j = 1; j <= n; j++) {
if (y[j] > mx) {
mx = y[j];
p = j;
}
}
y[p] = 0; // 同上
b2[p]++; // p这列是需要加入过道的
}
// 输出答案
for (int i = 1; i <= m; i++)
if (b1[i]) printf("%d ", i); // 表示需要隔开这行
puts("");
for (int i = 1; i <= n; i++)
if (b2[i]) printf("%d ", i);
return 0;
}

View File

@@ -0,0 +1,76 @@
## [$P1056$ [$NOIP2008$ 普及组] 排座椅 题解](https://www.luogu.com.cn/problem/P1056)
先想一下算法:因为题目里出现了 **最优解** , **最好的方案** 关键字,所以一定会用 **贪心**。然后从题目给的样例解释可以看到:如果相邻的两行有许多组说话的同学,那么在这两行中间加一条过道是非常划算的;同理,列也是如此。
恍然大悟,只要找出划分哪些相邻的两行和相邻的两列可以隔开的同学最多,此题可解。
接下来是找规律:
- 我们先定义两个数组$x,y$:
- $x[1]$表示如果在第一列与第二列中间划分过道能够分开几组说话的同学,同理,$x[2]$则是第二列与第三列... 直到$x[n-1]$
- $y[1]$表示第一行与第二行,$y[2]$表示第二行与第三行... 直到$y[m-1]$
- 题目输入两个同学的坐标,如果横坐标相同,即这两个同学在一行,那么设两个同学纵坐标分别为$a,b$ 如果$a<b$ 那么$x[a]++$ 否则$x[b]++$(这里一定要特判一下$a$和$b$的大小) 同理 $y$ 数组也如此操作即可。
- 最后$x,y$数组分别扫一遍,然后桶排一下即可。
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int m, n, k, l, d; // m行n列,k条横向的通道,l条纵向的通道,d对同学交头接耳
int y[N], x[N]; // 横纵坐标数组
int b1[N], b2[N]; // 桶排要用的数组
int main() {
cin >> m >> n >> k >> l >> d;
while (d--) {
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
if (x1 == x2) // 横坐标相等,则记录到个数数组中
y[min(y1, y2)]++; // 表示隔开这两排的价值
else
x[min(x1, x2)]++; // 记得取min,即过道与前一个坐标保持一致
}
for (int i = 1; i <= k; i++) { // 循环k次
int mx = 0; // 临时最大值
int p; // 临时最大值位置
for (int j = 1; j <= m; j++) {
if (x[j] > mx) {
mx = x[j];
p = j;
}
}
x[p] = 0; // 求出max之后一定要记得清零!!否则无论排多少次都是一个答案
b1[p]++; // p这行是需要加入过道的
}
for (int i = 1; i <= l; i++) {
int mx = 0;
int p;
for (int j = 1; j <= n; j++) {
if (y[j] > mx) {
mx = y[j];
p = j;
}
}
y[p] = 0; // 同上
b2[p]++; // p这列是需要加入过道的
}
// 输出答案
for (int i = 1; i <= m; i++)
if (b1[i]) printf("%d ", i); // 表示需要隔开这行
puts("");
for (int i = 1; i <= n; i++)
if (b2[i]) printf("%d ", i);
return 0;
}
```

View File

@@ -0,0 +1,33 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, x[N], y[N], dist[N], ans[N];
int main() {
cin >> n;
for (int i = 0; i < n; i++) cin >> x[i] >> y[i];
memset(ans, 0x3f, sizeof ans);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) { // 双重循环枚举每个已知的点
int X = x[i]; // 假设最终的结果是这个X
int Y = y[j]; // 假设最终的结果是这个Y
// 计算每个点到(X,Y)的汉密尔顿距离
for (int k = 0; k < n; k++)
dist[k] = abs(x[k] - X) + abs(y[k] - Y);
// 由小到大排序
sort(dist, dist + n);
int cnt = 0;
for (int k = 0; k < n; k++) {
cnt += dist[k]; // 累加每一个限定范围的最小距离和
ans[k] = min(ans[k], cnt);
}
}
}
// 输出答案
for (int i = 0; i < n; i++) printf("%d\n", ans[i]);
return 0;
}

View File

@@ -0,0 +1,49 @@
## [洛谷 $P1632$ 点的移动](https://www.luogu.com.cn/problem/P1632)
### 一、题目大意
求平面上 $1、2⋯n$ 个点的曼哈顿距离的最小值。
### 二、解题思路
枚举,我们假设 $m$ 个点的最小曼哈顿距离,我们假设汇集的点是 $(x,y)$ ,则
$x$ 必然可以选择 $n$ 个点的横坐标中的一个, $y$ 也可以选 $n$ 个点的纵坐标中的一个。
所以我们枚举 $x$ 和 $y$ 然后求距离即可。
### 三、$Code$
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, x[N], y[N], dist[N], ans[N];
int main() {
cin >> n;
for (int i = 0; i < n; i++) cin >> x[i] >> y[i];
memset(ans, 0x3f, sizeof ans);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) { // 双重循环枚举每个已知的点
int X = x[i]; // 假设最终的结果是这个X
int Y = y[j]; // 假设最终的结果是这个Y
// 计算每个点到(X,Y)的汉密尔顿距离
for (int k = 0; k < n; k++)
dist[k] = abs(x[k] - X) + abs(y[k] - Y);
// 由小到大排序
sort(dist, dist + n);
int cnt = 0;
for (int k = 0; k < n; k++) {
cnt += dist[k]; // 累加每一个限定范围的最小距离和
ans[k] = min(ans[k], cnt);
}
}
}
// 输出答案
for (int i = 0; i < n; i++) printf("%d\n", ans[i]);
return 0;
}
```

View File

@@ -0,0 +1,37 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 10005;
/*
文件名:petroleum
题目:洛谷 P1862 输油管道问题
题目网址:https://www.luogu.com.cn/problem/P1862
输入:
5
1 2
2 2
1 3
3 -2
3 3
输出:
6
*/
int n, a[N]; // a[0..n-1]。第i口油井的纵坐标在a[i]
int main() {
// 输入数据
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i] >> a[i]; // 横坐标说没有用的,可以直接抛弃掉
// 排序
sort(a, a + n);
// 两端对称取长短。中位数原理。
int sum = 0;
for (int i = 0, j = n - 1; i <= j; i++, j--) sum += a[j] - a[i];
cout << sum << endl;
return 0;
}

View File

@@ -0,0 +1,62 @@
## [洛谷 $P1862$ 输油管道问题](https://www.luogu.com.cn/problem/P1862)
![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/{year}/{month}/{md5}.{extName}/202309191036192.png)
如果只有一口井,那么显然是越近越好。如果有两口井,那么显然是有以下三种情况:
- 两口井都在主管道北边,那么这个时候的两个连接管道的长度和肯定大于两口井的$Y$坐标之差。
- 两口井都在主管道南边,和情况1是一样的
- 两口井,一个在主管道南边,一个在主管道北边,那么两个连接管道的长度和就等于两口井的$Y$坐标之差。
显然情况三是所要的最短管道的设计情况。就是当主管道在两口井之间的任意位置时,连接管道长度之和都等于两口井的$Y$坐标之差,是最短的长度。
那么将这个结论推广,当有$n$口井的时候,
- $n$是偶数 只要这$n$口井分布在主管道的两边,一边$n/2$个,那么就是距离之和最小的。
- $n$是奇数,只要将这$n$个井中,$Y$坐标最中间的(也就是$Y$是中值的那个)井不算,其余的偶数个井分布在主管道的两侧,这个时候移动主管道,那么这$n$个连接管道长度之和就决定于那个没有算的井了,因为其余的井的距离之和是固定了的,这个时候只要主管道最接近那个点就好了。
也就是说,输油管道的位置就是最中间的那口井(或中间的区间)。我的方法是将各口井的$y$坐标排序($x$坐标不用管),再取 $n$ $div$ $2$+$1$ 的位置,即最中间的位置。
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 10005;
/*
文件名:petroleum
题目:洛谷 P1862 输油管道问题
题目网址:https://www.luogu.com.cn/problem/P1862
输入:
5
1 2
2 2
1 3
3 -2
3 3
输出:
6
*/
int n, a[N]; // a[0..n-1]。第i口油井的纵坐标在a[i]
int main() {
// 输入数据
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i] >> a[i]; // 横坐标说没有用的,可以直接抛弃掉
// 排序
sort(a, a + n);
// 两端对称取长短。中位数原理。
int sum = 0;
for (int i = 0, j = n - 1; i <= j; i++, j--) sum += a[j] - a[i];
cout << sum << endl;
return 0;
}
```

View File

@@ -0,0 +1,24 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 10010;
int x[N], y[N];
int n, res;
int main() {
cin >> n;
for (int i = 0; i < n; i++) cin >> x[i] >> y[i];
sort(y, y + n);
sort(x, x + n);
for (int i = 0; i < n; i++) res += abs(y[i] - y[n / 2]); // 对与x轴的一条平等线的最短距离和
for (int i = 0; i < n; i++) x[i] -= i;
sort(x, x + n);
for (int i = 0; i < n; i++) res += abs(x[i] - x[n / 2]);
// 输出
cout << res << endl;
return 0;
}

View File

@@ -0,0 +1,52 @@
## [洛谷 $P1889$ 士兵站队](https://www.luogu.com.cn/problem/P1889)
![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/{year}/{month}/{md5}.{extName}/202309191122239.png)
### 问题简述
这道题我们可以换另一种思路去看待它,就容易理解了:
在一个平面上,把 $n$ 个点排列在一条与 $x$ 轴平行的直线的整点上,且相邻两点的距离为 $1$ 。
求一种排列方案,使得这$n$ 个点到目标位置的 **曼哈顿距离和最小**。
### 解法综述
由于是求曼哈顿距离,所以可以将 $x$ ,$y$ 分开考虑。
首先,要找到最优的一列(使移动步数最少的一列),其实就是要找一条平行于$x$轴的直线,设此直线为$y=k$,那么,每个点到这条线距离为$|y_i-k|$,不难发现,当$k$是所有点纵坐标的中位数时,距离之和最小。
找到了这条直线之后,又该把每个点移到哪个位置才能使结果最优呢?
可以设最左边的点(第一个点)移动后的位置为$b$,因为所有点必须排在一条线段上,那么第二个点的移动后的位置即为$b+1$,第三个移动后的位置为$b+2$......以此类推,第$n$个点移动后的位置为$b+n-1$。
那么横向移动的步数之和为$|x_0-b|+|x_1-b-1|+|x_2-b-2|+......+|x_{n-1}-b-n+1|$,所以,要使步数之和最小,只需要 **再找一次中位数** 即可。
### $Code$
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 10010;
int x[N], y[N];
int n, res;
int main() {
cin >> n;
for (int i = 0; i < n; i++) cin >> x[i] >> y[i];
sort(y, y + n);
sort(x, x + n);
for (int i = 0; i < n; i++) res += abs(y[i] - y[n / 2]); // 对与x轴的一条平等线的最短距离和
for (int i = 0; i < n; i++) x[i] -= i;
sort(x, x + n);
for (int i = 0; i < n; i++) res += abs(x[i] - x[n / 2]);
// 输出
cout << res << endl;
return 0;
}
```

View File

@@ -0,0 +1,18 @@
#include <bits/stdc++.h>
using namespace std;
const int N=30010;
int n,m;
int w[N];
int main() {
cin>>m>>n;
for (int i = 0; i < n; i++) cin >> w[i];
sort(w, w + n);
int res = 0;
for (int i = 0, j = n - 1; i <= j; j--) {
if (w[i] + w[j] <= m) i++;
res++;
}
cout << res << endl;
return 0;
}

View File

@@ -0,0 +1,17 @@
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 40;
ll c[N][N];
int main(){
for(int i = 0; i < N; i++){
for(int j = 1; j <= i; j++)
if(!j)c[i][j] = 1;
else
c[i][j] = c[i-1][j] + c[i-1][j-1];
}
int n;
cin >> n;
cout << c[n*2][n]/(n+1) << endl;
return 0;
}

View File

@@ -0,0 +1,7 @@
#### [洛谷 $P1862$ 输油管道问题](https://www.cnblogs.com/littlehb/p/17714420.html)
#### [洛谷 $P1889$ 士兵站队](https://www.cnblogs.com/littlehb/p/17714423.html)
#### [洛谷 $P1056$ [NOIP2008 普及组] 排座椅](https://www.cnblogs.com/littlehb/p/17714427.html)
#### [洛谷P1632 点的移动](https://www.cnblogs.com/littlehb/p/17714761.html)