「基础算法」第1章 递推算法强化训练

「基础算法」第1章 递推算法强化训练

A. 1.划分数列

题目

题目描述

给定一个长度为 n 的数列 A ,要求划分最少的段数,使得每一段要么单调不降,要么单调不升。

输入格式

第一行一个整数 n 。

接下来 n 个数表示数列 A 。

输出格式

输出最少的划分数。

样例

样例输入 1

6
1 2 3 2 2 1

样例输出 1

2

样例输入 2

9
1 2 1 2 1 2 1 2 1 

样例输出 2

5

样例输入 3

7
1 2 3 2 1 999999999 1000000000

样例输出 3

3

数据范围与提示

\[1\leq n \leq1e5,a_i在int范围内 \]

思路

挺简单的一道题:

\(up_i\)表示数列A中,\([up_i,i]\)这一段区间单调不下降,\(down_i\)表示数列A中,\([down_i,i]\)这一段区间单调不上升,\(f_i\)表示\([1,i]\)这一段区间内最小的合法划分段数.

显然(难道不显然吗),有递推式:

\[f_i=\min \begin{cases} f_{up_{i}-1}+1\\ f_{down_{i}-1}+1 \end{cases} \]

代码

#include <iostream>
#include <cstdio>
#define nn 100010
using namespace std;
int read() {
	int re = 0 , sig = 1;
	char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-')sig = -1;
		c = getchar();
	}
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0',
		c = getchar();
	return sig * re;
	
}
int f[nn];
int a[nn];
int up[nn] , down[nn];
int n;
#define min_(_ , __) (_ < __ ? _ : __)
int main() {
	up[0] = down[0] = 1;
	n = read();
	for(int i = 1 ; i <= n ; i++) {
		a[i] = read();
		if(a[i] == a[i - 1] || i == 1)
			up[i] = up[i - 1] , down[i] = down[i - 1];
		else
			if(a[i] > a[i - 1])
				up[i] = up[i  - 1] , down[i] = i;
			else
				up[i] = i , down[i] = down[i - 1];
	}
	
	for(int i = 1 ; i <= n ; i++)
		f[i] = min_(f[up[i] - 1] , f[down[i] - 1]) + 1;
	cout << f[n] << endl;
	return 0;
}

B. 2.求 f 函数

题意

给定函数

\[f(x)= \begin{cases} f(f(x+11)) &x\leq100\\ x-10 &x\geq101 \end{cases} \]

输入若干个x(\(x\in[0,1e6]\)),(以0结束),O(1)求出\(f(x)\)

其实数据水的很,直接暴力也能过

思路

看到当\(x\leq100\)时才需要递归,就毫不犹豫的把x=1~100的表打了出来,结果惊人的一幕出现了,输出的全是91

所以就有了以下代码:

int read() {//快读(主函数外)
	int re = 0 , sig = 1;
	char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-')sig = -1;
		c = getchar();
	}
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0',
		c = getchar();
	return sig * re;
	
}
//(主函数内)
while(x = read()) printf("%d\n" , x <= 100 ? 91 : x - 10);

结果,A了……

但问题是为什么呢?

从x=101开始,多推几次:

f(101)=91
f(100)=f(f(111))=f(101)=91
f(99)=f(f(110))=f(100)=91
f(98)=f(f(109))=f(9)=91
f(x)=f(x+1)=91
...
f(91)=91
f(90)=f(f(101))=f(91)=91
f(89)=f(f(100))=f(91)=91
...

这样,就得到了上面的结论,这道题也完美地解决了

代码

#include <iostream>
#include <cstdio>
using namespace std;
int read() {
	int re = 0 , sig = 1;
	char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-')sig = -1;
		c = getchar();
	}
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0',
		c = getchar();
	return sig * re;
	
}
int f(int x) {
	return x <= 100 ? f(f(x + 11)) : x - 10;
}
int x;
int main() {
	while(x = read()) printf("%d\n" , x <= 100 ? 91 : x - 10);
	return 0;
	//以下用于打表
	for(int i = 1 ; i <= 100 ; i++)
		cout << f(i) << " , ";
	while(x = read())printf("%d\n" , f(x));
	return 0;
}

C. 3.无限序列

题目

题目描述

我们按以下方式产生序列:

  1. 开始时序列是: 1
  2. 每一次变化把序列中的 1 变成 100 变成 1

经过无限次变化,我们得到序列 1011010110110101101 ...

总共有 Q 个询问,每次询问为:在区间 a 和 b 之间有多少个 1

任务:写一个程序回答 Q 个询问。

输入格式

输入的第一行为一个整数 Q ,后面有 Q 行,每行两个数用空格隔开的整数 a , b 。

输出格式

输出共 Q 行,每行一个回答。

样例

样例输入

1
2 8

样例输出

4

数据范围与提示

\[1\leq Q \leq5000,1\leq a \leq b <2^{63} \]

思路

依旧是找规律:

第1次变化(初始):1
第2次变化:10
第3次变化:101
第4次变化:10110
第5次变化:10110101

用程序多弄几次,可以发现:若用\(s(i)\)表示第i次变化后的结果,则有\(s(i)=s(i-1)+s(i-2) (i\geq 3)\)

因此,若用\(len(x)\)表示\(s(i)\)的长度,\(f(i)\)表示\(s(i)\)中"1"的数量,则显然有:

\[f(x)= \begin{cases} f(x-1)+f(x-2) &x\geq 3 \\ 1 &x=1\\ 1 &x=2 \end{cases} \\ len(x)= \begin{cases} len(x-1)+len(x-2) &x\geq 3 \\ 1 &x=1\\ 2 &x=2 \end{cases} \]

不难无限次变化后1~x位中"1"的个数为:

ll calc(ll x) {
	if(x == 0)return 0;//注意特判,否则RE
	for(int i = 1 ; i <= n ; i++) {
        //n=93,(len(n)刚好超过2^63而又不爆unsigned long long)
        //这里是为了枚举一个i,使f(i)>=x&&f(i-1)<x,其实可以二分,但是时间复杂度要求不高
		if(len[i] == x)return f[i];//x刚好等于s(i)的长度,直接返回即可
		if(len[i] > x) {
			--i;//为了方便
			return f[i] + calc(x - len[i]);//因为s(i)=s(i-1)+s(i-2),所以直接累加f(i-1)并向下递归即可
		}
	}
}

\([a,b]\)内"1"的数量就是:calc(b)-calc(a-1),就做完啦

由于a最小为1,a-1=0,所以calc()中要特判x==0的情况

代码

#include <iostream>
#include <cstdio>
#define ll unsigned long long
#define n 93
using namespace std;
ll len[110] , f[110];
ll calc(ll x) {
	if(x == 0)return 0;
	for(int i = 1 ; i <= n ; i++) {
		if(len[i] == x)return f[i];
		if(len[i] > x) {
			--i;
			return f[i] + calc(x - len[i]);
		}
	}
}
int main() {
	len[0] = 1 , len[1] = 1 , len[2] = 2;
	f[0] = 0 , f[1] = 1 , f[2] = 1;
	for(int i = 3 ; i <= n ; i++)
		f[i] = f[i - 1] + f[i - 2],
		len[i] = len[i - 1] + len[i - 2];
	
	int q;
	cin >> q;
	while(q--) {
		ll a , b;
		cin >> a >> b;
		cout << calc(b) - calc(a - 1) << endl;
	}
	return 0;
}

/*
10
6 13
21 81
25 77
9 37
29 89
48 97
41 65
65 85
42 98
17 73

5
38
33
18
38
31
15
13
36
35

*/

D,E:未完,待续……

posted @ 2020-12-25 20:35  追梦人1024  阅读(195)  评论(0编辑  收藏  举报