一.双指针和滑动窗口
没有思路的时候,想想要不要预排序或前缀和,或先前缀和然后预排序
1.Unique Snowflakes
分析:找没有重复数字的最大子序列。利用map来记录数的出现情况。r一直向右走,如果snow[r]还没有出现过,则将此数插入map并赋值为1,如果snow[r]出现过,说明找完了l在当前位置下与r的最大距离,也就是l不变时最长的子序列(如果l往右推进,可能找到更大的子序列),于是跳出while循环,记录当前的长度。然后l向右推进(l++),并在map中mp[snow[l]] = 0,表示现在snow[l]出现的次数归零。但有个问题,如果刚刚归零的数字不是重复的数字怎么办?没关系,下次再访问 while (!mp[snow[r]]) 的时候还是进不去循环(因为r没有变,依然指向重复的那个数字),然后继续l++,mp[snow[l]] = 0,直到l指向重复的数字的下一个位置
#include<stdlib.h>
#include<string>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#include<iostream>
using namespace std;
//#include<bits/stdc++.h>
//const int N = 1e6 + 10;
typedef long long ll;
int main() {
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<int> snow(n + 5, 0);
for (int i = 1; i <= n; i++) {
int a;
cin >> a;
snow[i] = a;
}
map<int, int> mp;
int res = -1;
int l = 1, r = 0;
while (r <= n) {
while (!mp[snow[r]]) {
//还不存在
mp[snow[r]] = 1;//插入记录
r++;
}
r--;
res = max(res, r - l + 1);
mp[snow[l]] = 0;
l++, r++;
}
cout << res << endl;
}
return 0;
}
二. 二分法
二分法,通常将一组数据排序后,将left指向头,right指向尾,mid = (left + right) / 2,判断mid是否符合题目所求,跟所求相比,如果mid太大了,则继续查找mid前面的部分,若mid太小了,则继续查找mid后面的部分,直到找到位置,注意约束条件通常是 l <= r 。以上所述只是最简单的模板格式,具体题目要具体分析
1.进击的奶牛
Sample 1
Input copy | Outputcopy |
---|---|
5 3 1 2 8 4 9 | 3 |
分析:我们要将所有牛放入隔间里,并让它们的距离尽可能大,放完后,此时相邻两头牛的最小距离就是我们要求的答案 ans。
left和right和mid都是代表牛之间的距离,要先搞清楚这三个数据的含义。因为 1 <= ans <= a[n] - a[1],故将 left = 1, right = a[n] - a[1]。
按照平常的思路,肯定是从left++,直到找到符合条件的left(即最小距离),本质上就是在[left,right]的区间找答案,但这样效率太低会超时,故用二分法查找。
接下来是核心代码,check函数。在隔板中,至少以span为间隔,将牛一头一头放进去,用count来计数。若count >= c(要求放入的牛),说明这个span可以满足要求,返回true,否则返回false
写的时候犯了两个错误:
1.在check中,count = 0
2.在主函数中,left = mid和right = mid
对于1,因为第一个隔间一定会放一头牛,所以count = 1。如果count = 0,后续又没有处理,就错误了
对于2,拿left = mid举例,因为mid已经符合条件了,我们要向后查找,故要把left推进到mid的后一格,即 left = mid + 1
#include<string>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#include<iostream>
using namespace std;
//#include<bits/stdc++.h>
//const int N = 1e6 + 10;
typedef long long ll;
int n, c;
int a[100005];
//错误:行35,61,64
bool check(int span) {
int count = 1;//记录可以装下的牛的数量()
int cow = a[1];
for (int i = 2; i <= n; i++) {
if (a[i] - cow >= span) {
count++;
cow = a[i];
}
}
if (count >= c) return true;
else return false;
}
int main() {
cin >> n >> c;//n间格子,c头牛
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
sort(a + 1, a + n + 1);
int left = 1, right = a[n] - a[1];
int ans = 0;
while (left <= right) {
int mid = (right + left) / 2;//mid代表所求的最大的最小间隔
if (check(mid)) {
//如果以mid为间隔大小,可以装的下c头牛
//则贪心一下,试试更小的间隔是否也可以装下c头牛
ans = mid;//保存一下当前的答案,若贪心成功,则还会更新,若贪心失败
left = mid + 1;//因为mid = (right - left) / 2,所以此操作可以让mid更大
}
else {
right = mid - 1;
}
}
cout << ans;
return 0;
}
2.跳石头
Sample 1
Inputcopy | Outputcopy |
---|---|
25 5 2 2 11 14 17 21 | 4 |
Hint
输入输出样例 1 说明
将与起点距离为 22和 1414 的两个岩石移走后,最短的跳跃距离为 44(从与起点距离 1717 的岩石跳到距离 2121 的岩石,或者从距离 2121 的岩石跳到终点)。
跟上一题差不多,只是check函数里,count和cur的具体含义变了。
#include<stdlib.h>
#include<string>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#include<iostream>
using namespace std;
//#include<bits/stdc++.h>
//const int N = 1e6 + 10;
typedef long long ll;
int L, N, M;//M是要移走的数量
int rock[50010];
bool check(int m) {
int count = 0;
int cur = 0;//当前的位置
for (int i = 1; i <= N + 1; i++) {//注意此处是N+1,要包含终点
if (rock[i] - cur < m) {
count++;
}
else {
cur = rock[i];
}
}
if (count <= M) return true;
else return false;
}
int main() {
cin >> L >> N >> M;
for (int i = 1; i <= N; i++) {
cin >> rock[i];
}
rock[N + 1] = L;//终点
int l = 1, r = L;//最短的跳跃距离的最大值
int ans = 0;
while (l <= r) {
int mid = (l + r) / 2;
if (check(mid)) {
l = mid + 1;
ans = mid;
}
else {
r = mid - 1;
}
}
cout << ans;
return 0;
}
3.Monthly Expense
给出农夫在n天中每天的花费,要求把这n天分作m组,每组的天数必然是连续的,要求分得各组的花费之和应该尽可能地小,最后输出各组花费之和中的最大值
输入格式
Line 1: Two space-separated integers: N and M
Lines 2..N+1: Line i+1 contains the number of dollars Farmer John spends on the ith day
输出格式
Line 1: The smallest possible monthly limit Farmer John can afford to live with.
输入输出样例
输入 #1复制
7 5 100 400 300 100 500 101 400
输出 #1复制
500
说明/提示
If Farmer John schedules the months so that the first two days are a month, the third and fourth are a month, and the last three are their own months, he spends at most $500 in any month. Any other method of scheduling gives a larger minimum monthly limit.
#include<string>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#include<iostream>
using namespace std;
//const int N = 1e6 + 10;
typedef long long ll;
int n, m;
int a[100010];
bool check(int mid) {
int count = 0;//若mid为每组最大和,最少可以分几组
int sum = 0;
for (int i = 1; i <= n; i++) {
if (sum + a[i] <= mid) {
sum += a[i];
}//这里有疑问
else {
//不能再加了,再加就比mid大了
count++;
sum = a[i];//重新开了一组
}
}
if (count >= m) {
//太多组了,mid要大一点
return true;
}
else return false;
}
int main() {
cin >> n >> m;
int l = 0, r = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
l = max(l, a[i]);//原本没有这一步,wa了
r += a[i];
}
int ans;
while (l <= r) {
int mid = (l + r) / 2;
if (check(mid)) {
l = mid + 1;
ans = l;//为什么这里是l
}
else {
r = mid - 1;
ans = l;
}
}
cout << ans;
return 0;
}
4.Pie
描述 我的生日要到了!根据习俗,我需要将一些派分给大家。我有N个不同口味、不同大小的派。有F个朋友会来参加我的派对,每个人会拿到一块派(必须一个派的一块,不能由几个派的小块拼成;可以是一整个派)。
我的朋友们都特别小气,如果有人拿到更大的一块,就会开始抱怨。因此所有人拿到的派是同样大小的(但不需要是同样形状的),虽然这样有些派会被浪费,但总比搞砸整个派对好。当然,我也要给自己留一块,而这一块也要和其他人的同样大小。
请问我们每个人拿到的派最大是多少?每个派都是一个高为1,半径不等的圆柱体。
输入 第一行包含两个正整数N和F,1 ≤ N, F ≤ 10 000,表示派的数量和朋友的数量。 第二行包含N个1到10000之间的整数,表示每个派的半径。 输出 输出每个人能得到的最大的派的体积,精确到小数点后三位
输入 #1复制
3 3 3 4 3 3 1 24 5 10 5 1 4 2 3 4 5 6 5 4 2
输出 #1复制
25.1327 3.1416 50.2655
注意,这题关乎精度,于是约束条件不再是 l<=r,而是r-l>1e-4(二分板子)。为什么呢?
看到别的题经过check函数判断后,一般是 l = mid + 1或 r = mid - 1,但这题的蛋糕面积显然不能以1为间隔,否则精度不够,可能错过正确答案。
比如:此时l = 3,mid = 4,正确答案为4.5,如果用 l = mid + 1 = 5,那么接下来的二分永远不会遍历到4.5,错过正确答案
#include<assert.h>
#include<cstdio>
#include<set>
#include<list>
#include<queue>
#include<math.h>
#include<stdlib.h>
#include<string>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#include<iostream>
#define Pi 3.14159265358979323846
using namespace std;
int n,f,t;//n和f如题意,t是数据总数
double num,a[100005];//num是每个派的半径,a数组是每个派的体积
int check(double x)//check函数是用来判断二分是否可行的
{
int sum=0;//sum是能分的派的数量
for(int i=1;i<=n;i++)
{
sum+=(int)(a[i]/x);//强制转成int,向下取整
}
return sum>=f;//返回结果
}
int main()
{
scanf("%d",&t);
while(t--)
{
double l=0,r=0,mid,ans=0;//l,r和mid分别是二分的左右区间和当前枚举的位置,ans是答案
scanf("%d%d",&n,&f);
f++;//算上自己
for(int i=1;i<=n;i++)
{
scanf("%lf",&num);
a[i]=num*num*Pi;//计算体积(高度为1,忽略不计)
r=max(r,a[i]);//扩大右边界
}
while(r-l>0.0001)//二分板子(精度1e-4就够了)
{
mid=(l+r)/2;
if(check(mid))//如果可行
{
l=mid;//扩大上界
ans=max(ans,mid);//更新答案
}
else//如果不可行
{
r=mid;//缩小下界
}
}
printf("%.4f\n",ans);//输出答案
}
return 0;//结束程序
}
5.奶牛晒衣服
分析:一开始的思路是贪心+模拟,从湿度最大的衣服开始。但用sort排序会超时
正确做法是二分。
那这个做法为啥不用排序呢?
因为我们不关心哪件衣服的湿度更大,我们只需知道把这件衣服弄干需要多长时间 ti。
然后把所有ti相加,就是弄干所有衣服的时间
需要注意的是,把衣服放入烘干机不一定要一直烘到干再拿出来(因为这样的话就会浪费自然烘干的时间)
#include<set>
#include<list>
#include<queue>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<string.h>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#define inf 0x3f3f3f3f
#define int long long
const int N = 5e5 + 10;
//#include <bits/stdc++.h>
typedef long long ll;
#include<iostream>
using namespace std;
long long MAX(long long a, long long b) { return a < b ? b : a; }
int c[N];//湿度
int n, a, b;//自然干,烘干机
bool check(int mid) {
int k = 0;//时间
for (int i = 1; i <= n; i++) {
if (mid * a >= c[i]) {
//用不上烘干机,可以自然干
;
}
else {
int temp = c[i] - mid * a;
k += temp / b;
if (temp - (temp / b) * b > 0) k += 1;
}
}
if (k <= mid) return true;
else return false;
}
signed main()
{
cin >> n >> a >> b;
for (int i = 1; i <= n; i++) {
cin >> c[i];
}
//sort(a + 1, a + n + 1);
int l = 1, r = N;
int ans = 1;
while (l <= r) {
int mid = (l + r) / 2;//时间想尽可能小
if (check(mid)) {
//可以晒得干
ans = mid;
r = mid - 1;
}
else {
l = mid + 1;
}
}
cout << ans;
return 0;
}
6.kotori的设备
输入 #1复制
2 1 2 2 2 1000
输出 #1复制
2.0000000000
输入 #2复制
1 100 1 1
输出 #2复制
-1
输入 #3复制
3 5 4 3 5 2 6 1
输出 #3复制
0.5000000000
分析:跟上一题差不多。要关注需要充电的设备,并算出所需充电量是多少。
注意!
1.因为涉及精度,所以该用double就用double,别写成int了
2.二分板子精度是1e-6,要记住
3.二分的右端,在此题没有体现,可以写1e10
4.像这种精度比较高的题,直接写l = mid和r = mid就好了,否则会损失精度
但是这样写也行
#include<set>
#include<list>
#include<queue>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<string.h>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#define inf 0x3f3f3f3f
#define int long long
const int N = 1e5 + 50;
//#include <bits/stdc++.h>
typedef long long ll;
#include<iostream>
using namespace std;
long long MAX(long long a, long long b) { return a < b ? b : a; }
int n, p;
const double MMIN = 1e-6;//控制精度
const int MMAX = 1e10;
struct Node {
double cost, b;//消耗,原本有的电量
}node[N];
bool check(double mid) {
double sum = 0;//需要冲的电
for (int i = 1; i <= n; i++) {
double temp = node[i].cost * mid;
if (temp > node[i].b) sum += temp - node[i].b;
}
if (sum > p * mid) return false;
else return true;
}
signed main()
{
cin >> n >> p;
double sum = 0;
for (int i = 1; i <= n; i++) {
cin >> node[i].cost >> node[i].b;
sum += node[i].cost;
}
double l = 0, r = MMAX, mid;
//二分的右端是1e10,要记住了
if (sum <= p) cout << -1;
else {
double ans = 0;
while (r - l >= MMIN) {
mid = (r + l) / 2;
if (check(mid)) {
ans = mid;
l = mid;//需要控制精度的话直接等于mid
}
else {
r = mid;
}
}
printf("%.10f", l);
}
return 0;
}
7.数列分段 Section II
# 数列分段 Section II
## 题目描述
对于给定的一个长度为N的正整数数列 $A_{1\sim N}$,现要将其分成 $M$($M\leq N$)段,并要求每段连续,且每段和的最大值最小。
关于最大值最小:
例如一数列 $4\ 2\ 4\ 5\ 1$ 要分成 $3$ 段。
将其如下分段:
$$[4\ 2][4\ 5][1]$$
第一段和为 $6$,第 $2$ 段和为 $9$,第 $3$ 段和为 $1$,和最大值为 $9$。
将其如下分段:
$$[4][2\ 4][5\ 1]$$
第一段和为 $4$,第 $2$ 段和为 $6$,第 $3$ 段和为 $6$,和最大值为 $6$。
并且无论如何分段,最大值不会小于 $6$。
所以可以得到要将数列 $4\ 2\ 4\ 5\ 1$ 要分成 $3$ 段,每段和的最大值最小为 $6$。
## 输入格式
第 $1$ 行包含两个正整数 $N,M$。
第 $2$ 行包含 $N$ 个空格隔开的非负整数 $A_i$,含义如题目所述。
## 输出格式
一个正整数,即每段和最大值最小为多少。
## 样例 #1
### 样例输入 #1
```
5 3
4 2 4 5 1
```
### 样例输出 #1
```
6
```
#include<set>
#include<list>
#include<queue>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<string.h>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#define inf 0x3f3f3f3f
#define int long long
const int N = 1e5 + 50;
//#include <bits/stdc++.h>
typedef long long ll;
#include<iostream>
using namespace std;
long long MAX(long long a, long long b) { return a < b ? b : a; }
int n, m;
int a[N];
bool check(double mid) {
int sum = 0;
int cnt = 1;//注意计数,就是错在这个地方
for (int i = 1; i <= n; i++) {
if (sum + a[i] > mid) {
sum = 0;
cnt++;
}
sum += a[i];
}
return cnt <= m;
}
signed main()
{
cin >> n >> m;
int l = 0, r = 0, mid, ans = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
l = max(l, a[i]);//这题l不能赋值为0或1,会wa
r += a[i];
}
while (l <= r) {
mid = (l + r) / 2;
if (check(mid)) {
r = mid - 1;
ans = mid;
}
else {
l = mid + 1;
}
}
cout << ans;
return 0;
}
三. 前缀和与差分
差分是前缀和的逆运算。例如有一个数组arr = {1,2,3,4,5},arr的前缀和数组为 sum = {1,3,6,10,15},那么arr就是sum的差分数组,sum是arr的前缀和数组。
易知,
arr[0] = sum[0]
arr[1] = sum[1] - sum[0]
arr[2] = sum[2] - sum[1]
......
arr[n] = sum[n] - sum[n-1]
一维差分中,差分就是将数列中的每一项分别与前一项数做差。
ok,现在给你一个数组,求这个数组的差分数组
例如 1 2 3 4 5
差分后 1 1 1 1 1
引入一个问题:给一个数组a,让a的[l,r]的元素都加上c。
差分的做法是,先构建一个差分数组b,在b[l] += c,于是a的[l,end]的元素都加c,但我们要的是r后面的元素不变,所以 b[r+1] -= c,然后再对这个调整后的b数组构建前缀和数组,即为数组a的[l,r]都加上c结果。
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N],b[N];
int main()
{
int n,m;
scanf("%d%d", &n, &m);
for(int i = 1;i <= n; i++)
{
scanf("%d", &a[i]);
b[i] = a[i] - a[i - 1]; //构建差分数组
}
int l, r, c;
while(m--)
{
scanf("%d%d%d", &l, &r, &c);
b[l] += c; //表示将序列中[l, r]之间的每个数加上c
b[r + 1] -= c;
}
for(int i = 1;i <= n; i++)
{
b[i] += b[i - 1]; //求前缀和运算
printf("%d ",b[i]);
}
return 0;
}
1.Tallest Cow
输入 #1复制
9 3 5 5 1 3 5 3 4 3 3 7 9 8
输出 #1复制
5 4 5 3 4 4 5 5 5
分析:先假设所有牛都为最高值,然后对于每次给出的a和b,把给出的a,b之间的牛的高度都减1(稍后解释)。注意,例如3和5跟5和3是一样的数据,不必重复减。
因此用map来记录这对数据是否出现过,map的新用法,要记住。可以用bool数组(初始默认为false)
为什么每次都要把a,b间的牛的高度都减1?根据题意,a和b间的牛必然小于a和b的高度,所以要减1来符合题意。
那有个问题,如果a和b之间有牛本身就小于a,b,还需要减1吗?需要!
让我们来分析一下样例
const int N = 1e6 + 10;
typedef long long ll;
int a[N];
int b[N];//差分数组
int main() {
int n, highest_index, highest, R;
cin >> n >> highest_index >> highest >> R;
//memset(a, highest, sizeof(a));
for (int i = 1; i <= n; i++) {
a[i] = highest;
}
for (int i = 1; i <= n; i++) {
b[i] = a[i] - a[i - 1];
}
map<pair<int, int>, bool> mp;
for (int i = 0; i < R; i++) {
int l, r;//注意,3和5,5和3是一样的,不用重复计算
cin >> l >> r;
int temp = l;
l = min(l, r);
r = max(temp, r);
if (mp[make_pair(l,r)]) {
continue;
}
mp[make_pair(l, r)] = true;
b[l + 1] -= 1;
b[r] += 1;
}
for (int i = 1; i <= n; i++) {
a[i] = b[i] + a[i - 1];
cout << a[i] << endl;
}
return 0;
}
2.IncDec Sequence
Sample 1
Inputcopy | Outputcopy |
---|---|
4 1 1 2 2 | 1 2 |
分析:假如数组a的的所有元素都一样大,a = {5,5,5,5,5},那么a的差分数组 b = {5,0,0,0,0}。
因此得到了启发,
1.如果要将一个数组调整成所有元素一样大,那么要将其差分数组(除了第一个元素)的元素都调整为0。
2.而且注意到题中要求,每次操作是将a的[l,r]区间+1或-1。这个操作可以用差分数组实现,所以问题转化成,在差分数组b中找一个l和r,b[l]++,b[r+1]--。
首先我们要知道,
在对原数组a的[l,r]进行+1操作时,对差分数组b来说是进行了两个操作,即 b[l]++,b[r+1]--。
(我们称作“一步顶两步”)
但如果[l,r]中的r是数组的末尾,那对差分数组b来说是进行了一个操作,即b[l]++。
(我们称作“一步顶一步”)
那么我们最少进行多少种这样的操作呢?
因为我们要除了b[1]以外的元素都变成0,
我们可以令差分序列里正数绝对值的总和为p,负数绝对值总和为q
也就是说,在差分数组里,要做p次-1的操作,q次+1的操作。
所以我们先做“一步顶两步的操作”,不断使p-1,q+1。
但p和q总有一个先变成0,那接下来就要做“一步顶一步”的操作,使还不是0的那一个变成0
因此最少操作数为 min(p,q)+abs(p-q)
第二问:有多少种不同序列呢?
因为差分数组b除了b[1]都必须为0,那么只要b[1]改变,就会改变整个序列。换言之,可以有多少个不同的b[1]?是abs(p-q)+1个。因为在做“一步顶一步”的操作时,才能有余力修改b[1]。
当然了,既然还要修改b,那么“一步顶一步”的操作也就变成了做“一步顶两步”的操作。
那为什么还要+1呢?因为如果你不修改b[1]的话,也是一种情况。
#include<queue>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<string.h>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#define inf 0x3f3f3f3f
#define int long long
const int N =1e6 + 10;
//#include <bits/stdc++.h>
typedef long long ll;
#include<iostream>
using namespace std;
long long MAX(long long a, long long b) { return a < b ? b : a; }
int a[N], b[N];
signed main()
{
int n;
cin >> n;
int p = 0, q = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
b[i] = a[i] - a[i - 1];
if (i != 1) {
if (b[i] >= 0) p += b[i];
else q -= b[i];
}
}
int op = min(p, q) + abs(p - q);
int way = abs(p - q) + 1;
cout << op << endl << way;
return 0;
}
3.Subsequences Summing to Sevens S
注意用first和last数组记录相同余数出现的第一次和最后一次的位置
注意要 first[0] = 0,因为?
#include<queue>
#include<math.h>
#include<stdlib.h>
#include<string>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#include<iostream>
using namespace std;
//#include<bits/stdc++.h>
const int N = 1e6 + 10;
typedef long long ll;
int a[N];
int b[N];//差分数组
//如果(a - b)% c = 0,那么 a%c = b%c
int first[7];//下标代表余数,数组存的是某个余数出现的位置
int last[7];
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
a[i] = (a[i - 1] + x) % 7;
}
for (int i = n; i >= 1; i--) {
first[a[i]] = i;
}
for (int i = 1; i <= n; i++) {
last[a[i]] = i;
}
int len = -1;
first[0] = 0;
for (int i = 0; i < 7; i++) {
len = max(len, last[i] - first[i]);
}
if (len == -1) cout << 0;
else cout << len;
return 0;
}
4.海底高铁
Sample 1
Inputcopy | Outputcopy |
---|---|
9 10 3 1 4 1 5 9 2 6 5 3 200 100 50 300 299 100 500 200 500 345 234 123 100 50 100 600 100 1 450 400 80 2 1 10 |
分析:记录每个城市经过的次数,最后计算值不值得买ic卡
int p[100005], a[100005], b[100005], c[100005];
long long s, ans[100005];//前缀和必须用long long,不然最后三个点会WA
int main()
{
int n, m, i;
cin >> n >> m;
for (i = 1; i <= m; i++)
cin >> p[i];
for (i = 1; i < n; i++)
cin >> a[i] >> b[i] >> c[i];
for (i = 1; i <= m - 1; i++)//对于每一段路程,开头+1,结尾-1标记一下
{
ans[min(p[i], p[i + 1])]++;
ans[max(p[i], p[i + 1])]--;
}
for (i = 1; i <= n; i++)//然后累加求出每一项的前缀和
ans[i] += ans[i - 1];
for (i = 1; i < n; i++)//判断买票和办卡哪个便宜,算出总花费
s += min(a[i] * ans[i], (b[i] * ans[i] + c[i]));
cout << s << endl;//输出总价
return 0;//完结撒花~
}
四. 倍增与ST算法
作用:ST表是用于查询区间[l,r]的最大值或最小值,简称RMQ问题
原理:ST表会保存所有子区间的最值。
看例题
1.ST表
Sample 1
Inputcopy | Outputcopy |
---|---|
8 8 9 3 1 7 5 6 0 8 1 6 1 5 2 7 2 6 1 8 4 8 3 7 1 8 | 9 9 7 7 9 8 7 9 |
#include<math.h>
#include<stdlib.h>
#include<string>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#include<iostream>
using namespace std;
const int N = 1e6 + 10;
//typedef long long ll;
//int a[N];
//int b[N];//差分数组
int F[N][21];//2的21次幂,够大够用了
int main() {
int n, m;//数组长度,询问个数
scanf("%d%d", &n, &m);
//构建ST表
for (int i = 1; i <= n; i++) {
int c;
scanf("%d", &c);
F[i][0] = c;//先计算长度为1的子区间最值,也就是这个数本身
}
//动态规划构建ST表
int k = (int)(log(n) / log(2));//向下取整
for (int j = 1; j <= k; j++) {
for (int i = 1; i + (1 << j) - 1 <= n ; i++) {
//这个条件保证了在每一层 j(j 的范围是从 1 到 k)中,对于当前的起点位置 i,它的区间
//长度为 2^j 的子区间的右边界不会超过原始数据集合的长度 n。如图解释
F[i][j] = max(F[i][j - 1], F[i + (1 << (j - 1))][j - 1]);
}
}
for (int i = 0; i < m; i++) {
int l, r;
scanf("%d%d", &l, &r);
k = (int)(log(r - l + 1) / log(2));//计算区间[l,r]的长度
printf("%d\n", max(F[l][k], F[r - (1 << k) + 1][k]));
}
return 0;
}
五. 分治法
1.Fractal
A fractal is an object or quantity that displays self-similarity, in a somewhat technical sense, on all scales. The object need not exhibit exactly the same structure at all scales, but the same "type" of structures must appear on all scales.
A box fractal is defined as below :
- A box fractal of degree 1 is simply
X - A box fractal of degree 2 is
X X
X
X X - If using B(n - 1) to represent the box fractal of degree n - 1, then a box fractal of degree n is defined recursively as following
B(n - 1) B(n - 1) B(n - 1) B(n - 1) B(n - 1)
Your task is to draw a box fractal of degree n.
Input
The input consists of several test cases. Each line of the input contains a positive integer n which is no greater than 7. The last line of input is a negative integer −1 indicating the end of input.
Output
For each test case, output the box fractal using the 'X' notation. Please notice that 'X' is an uppercase letter. Print a line with only a single dash after each test case.
Sample
Inputcopy | Outputcopy |
---|---|
1 2 3 4 -1 | X - X X X X X - X X X X X X X X X X X X X X X X X X X X X X X X X - X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X - |
分析:经过观察得知,图形一共有五个部分组成,左上,右上,中间,左下,右下。
且图形边长 = pow(3,n-1)
假设要打印为n的图形,那么该图形的五个部分都是 n-1 的图形,不难想到分治递归,但由于打印时不能控制起点(除非用gotoxy),所以要事先将图形存入二维数组然后打印。
因为 n<=7,n很小,直接把n=7的情况,最大的图形预处理出来,存入二维数组。
对于输入的n,按需打印。
char ch[1000][1000];
int len[] = { 0,1,3,9,27,81,243,729 };
//因为n<=7,所以先把n所有对应的图形边长记录下来
void Init(int n, int x, int y) {
//以(x,y)为起点,初始化边长为n的‘X’图形
if (n == 1) {
ch[x][y] = 'X';
return;
}
int k = len[n - 1];//k为空格长度
Init(n - 1, x, y);
Init(n - 1, x + 2 * k, y);
Init(n - 1, x + k, y + k);
Init(n - 1, x, y + 2 * k);
Init(n - 1, x + 2 * k, y + 2 * k);
}
void Print(int n) {
for (int i = 0; i < len[n]; i++) {
for (int j = 0; j < len[n]; j++) {
cout << ch[i][j];
}
cout << endl;
}
cout << "-" << endl;
}
int main()
{
int n;
//图形的宽度和高度为pow(3,n-1)
memset(ch, ' ', sizeof(ch));
//记得要先全部初始化为空格,因为打印图形的过程中也会打印到空格
Init(7, 0, 0);
while (cin >> n && n != -1) {
Print(n);
}
return 0;
}
不预处理也行,没什么区别,但这题比较特殊,不预处理的做法可能能具普适性
using namespace std;
//#include<bits/stdc++.h>
const int N = 1e6 + 10;
typedef long long ll;
long long a[N];
//int b[N];//差分数组
int F[N][21];//找最矮
char ch[1000][1000];
int len[] = { 0,1,3,9,27,81,243,729 };
//因为n<=7,所以先把n所有对应的图形边长记录下来
void Init(int n, int x, int y) {
//以(x,y)为起点,初始化边长为n的‘X’图形
if (n == 1) {
ch[x][y] = 'X';
return;
}
int k = len[n - 1];//k为空格长度
Init(n - 1, x, y);
Init(n - 1, x + 2 * k, y);
Init(n - 1, x + k, y + k);
Init(n - 1, x, y + 2 * k);
Init(n - 1, x + 2 * k, y + 2 * k);
}
void Print(int n) {
for (int i = 0; i < len[n]; i++) {
for (int j = 0; j < len[n]; j++) {
cout << ch[i][j];
}
cout << endl;
}
cout << "-" << endl;
}
int main()
{
int n;
//图形的宽度和高度为pow(3,n-1)
memset(ch, ' ', sizeof(ch));
//记得要先全部初始化为空格,因为打印图形的过程中也会打印到空格
while (cin >> n && n != -1) {
Init(n, 0, 0);
Print(n);
}
return 0;
}
2.南蛮图腾
1<= n <= 10
Sample 1
Inputcopy | Outputcopy |
---|---|
2 | /\ /__\ /\ /\ /__\/__\ |
Sample 2
Inputcopy | Outputcopy |
---|---|
3 | /\ /__\ /\ /\ /__\/__\ /\ /\ /__\ /__\ /\ /\ /\ /\ /__\/__\/__\/__\ |
跟上一题思路一模一样
#include<stdlib.h>
#include<string>
#include<string.h>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#include<iostream>
using namespace std;
//#include<bits/stdc++.h>
const int N = 1e6 + 10;
typedef long long ll;
long long a[N];
//int b[N];//差分数组
int F[N][21];//找最矮
char ch[2500][2500];
void Init(int n, int row, int col) {//列,行
if (n == 1) {
ch[row][col] = '\\';
ch[row][col - 1] = '/';
ch[row + 1][col] = '_';
ch[row + 1][col - 1] = '_';
ch[row + 1][col + 1] = '\\';
ch[row + 1][col - 2] = '/';
return;
}
int len = pow(2, n - 1);
int wide = 4 * len, high = 2 * len;
Init(n - 1, row, col);
Init(n - 1, row + high / 2, col - wide / 4);
Init(n - 1, row + high / 2, col + wide / 4);
}
void Print(int len) {
for (int i = 0; i < 2 * len; i++) {
for (int j = 0; j < 4 * len; j++) {
cout << ch[i][j];
}
cout << endl;
}
}
int main()
{
int n;
cin >> n;
memset(ch, ' ', sizeof(ch));
int len = pow(2, n - 1);
Init(n, 0, 2 * len);
Print(len);
return 0;
}
3.最大子段和
基础动态规划,不是选与不选当前数的问题!而是要不要舍弃前面的连续段的问题!
但是,做的时候错了,陷入了一个误区。
我以为本题dp[i] 的的含义是在 [0,i] 中最大子串和,于是一股脑将最后输出写成 dp[n-1],错了。
因为dp[i]的真正含义是,包括当前i的子串的最大和,注意,是包括i的子串。因此要用sum记录最优解。
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#include<iostream>
using namespace std;
const int N = 1e6 + 10;
typedef long long ll;
int a[N];
//int b[N];//差分数组
int F[N][21];//找最矮
int dp[N];
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> a[i];
}
dp[0] = a[0];
int sum = -N;
for (int i = 1; i < n; i++) {
dp[i] = max(dp[i - 1] + a[i], a[i]);
sum = max(sum, dp[i]);
}
cout << sum;
return 0;
}
4.求第 k 小的数
用快速排序。第一次排序完后,判断k是在pivot前还是在pivot后,然后选择排序pivot前还是后,节省时间
#include<stdlib.h>
#include<string>
#include<string.h>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#include<iostream>
using namespace std;
//#include<bits/stdc++.h>
const int N = 1e6 + 10;
typedef long long ll;
int a[N];
//int b[N];//差分数组
int F[N][21];//找最矮
//快排,挖坑法
void QuickSort(int begin, int end, int k) {
if (begin >= end) return;
int key = a[begin];
int pivot = begin;
int l = begin, r = end;
while (l < r) {//当l==r时,这个地方就是坑,应该立即跳出循环,下面的也同理
//排升序,左边找大,右边找小
while (l < r && a[r] >= key) {
r--;
}
a[pivot] = a[r];
pivot = r;
while (l < r && a[l] <= key) {
l++;
}
a[pivot] = a[l];
pivot = l;
}
a[pivot] = key;//填坑
if (k == pivot) cout << a[pivot], exit(0);
else if (k < pivot) QuickSort(begin, pivot - 1, k);
else QuickSort(pivot + 1, end, k);
}
int main() {
int n, k;
cin >> n >> k;
for (int i = 0; i < n; i++) {
cin >> a[i];
}
QuickSort(0, n - 1, k);
return 0;
}
也可以用 nth_element 库函数
#include <stdio.h>
#include<algorithm>
#include<iostream>
using namespace std;
int x[5000005],k;
int main()
{
int n;
scanf("%d%d",&n,&k);
for(int i=0;i<n;i++)
scanf("%d",&x[i]);
nth_element(x,x+k,x+n);//简短又高效
printf("%d",x[k]);
}
5.逆序对
Sample 1
Inputcopy | Outputcopy |
---|---|
6 5 4 2 6 3 1 | 11 |
分析:用归并排序,让左右区间有序,然后计算逆序对
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#include<iostream>
#include<cstdio>
using namespace std;
const int N = 1e6 + 10;
typedef long long ll;
//int a[N];
//int b[N];//差分数组
int F[N][21];//找最矮
int temp[N];
ll cnt = 0;
int n, a[500010], c[500010];
long long ans;
void msort(int b, int e)//归并排序
{
if (b == e)
return;
int mid = (b + e) / 2, i = b, j = mid + 1, k = b;
msort(b, mid), msort(mid + 1, e);
while (i <= mid && j <= e)
if (a[i] <= a[j])
c[k++] = a[i++];
else
c[k++] = a[j++], ans += mid - i + 1;//统计答案
while (i <= mid)
c[k++] = a[i++];
while (j <= e)
c[k++] = a[j++];
for (int l = b; l <= e; l++)
a[l] = c[l];
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
msort(1,n);
printf("%lld",ans);
return 0;
}