集训第三周

一.双指针和滑动窗口

没有思路的时候,想想要不要预排序或前缀和,或先前缀和然后预排序

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 copyOutputcopy
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

InputcopyOutputcopy
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

InputcopyOutputcopy
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

InputcopyOutputcopy
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

InputcopyOutputcopy
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

InputcopyOutputcopy
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

InputcopyOutputcopy
2
   /\
  /__\
 /\  /\
/__\/__\

Sample 2

InputcopyOutputcopy
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

InputcopyOutputcopy
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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值