「基础算法」第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
数据范围与提示
思路
挺简单的一道题:
设\(up_i\)表示数列A中,\([up_i,i]\)这一段区间单调不下降,\(down_i\)表示数列A中,\([down_i,i]\)这一段区间单调不上升,\(f_i\)表示\([1,i]\)这一段区间内最小的合法划分段数.
显然(难道不显然吗),有递推式:
代码
#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 函数
题意
给定函数
输入若干个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
变成10
,0
变成1
。
经过无限次变化,我们得到序列 1011010110110101101 ...
。
总共有 Q 个询问,每次询问为:在区间 a 和 b 之间有多少个 1
。
任务:写一个程序回答 Q 个询问。
输入格式
输入的第一行为一个整数 Q ,后面有 Q 行,每行两个数用空格隔开的整数 a , b 。
输出格式
输出共 Q 行,每行一个回答。
样例
样例输入
1
2 8
样例输出
4
数据范围与提示
思路
依旧是找规律:
第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"的数量,则显然有:
不难无限次变化后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
*/