蓝桥杯中的题目合集
探索迷宫,看是否能够找到一条通路,S
为起始点,T
为终点,通路为.
,障碍为*
第一行输入m n
, 分别表示迷宫的行数和列数
下面一次输入迷宫图形
5 6
....S*
.***..
.*..*.
*.***.
.T....
问题思路
找到通路DFS算法:
1、如果当前已经到达终点,return结果为true
否则
2、标记当前结点已经走过,当前结点记号变换
从当前结点进行四个方向查找,如果下一个位置合法(在图内,能够到达,没有访问过),则继续DFS
如果在当前结点出发成功到达终点,返回true
3、当前结点四个方向不能到达则
恢复访问标记为未访问,恢复初始符号
找到最短通路算法:
(设定已经找到的步数为一个很大的值)
1、如果当前步数大于已经找到的步数,return
2、如果当前位置为终点,并且步数小与已经找到通路的步数,记录步数,return
3、标记当前结点已经访问
从当前结点开始,向四个方向继续寻找
恢复访问标记为未访问
#include
#include
#include
using namespace std;
#define MAXN 110
char maze[MAXN][MAXN];
int vis[MAXN][MAXN];
int ans = 1e9;
int m, n;
int dir[4][2] = { {-1, 0}, {0, 1}, {1, 0}, {0, -1} };
bool in(int x,int y) {
return x >= 0 && x < m && y >= 0 && y < n && maze[x][y] != '*';
}
void dfs(int x, int y, int step) {
if(step > ans) return;
if(maze[x][y] == 'T') {
ans = step;
return;
}
vis[x][y] = 1;
for(int i = 0; i < 4; i++){
int tx = x + dir[i][0];
int ty = y + dir[i][1];
if(in(tx, ty) && !vis[tx][ty] )
dfs(tx, ty, step + 1);
}
//not find
vis[x][y] = 0;
}
int main() {
memset(vis, 0, sizeof(vis));
int i, j, sx = 0, sy = 0;
cin >> m >> n;
for(i = 0; i < m; i++) {
cin >> maze[i];
}
for(sx = 0, sy = 0, i = 0; i < m; i++){
for(j = 0; j < n; j++) {
if(maze[i][j] == 'S'){
sx = i, sy = j;
j = n, i = m;
}
}
}
dfs(sx, sy, 0);
cout << ans << endl;
system("pause");
return 0;
}
详细算法
bool dfs(int x, int y) {
if(maze[x][y] == 'T') return true;
vis[x][y] = 1;
maze[x][y] = 'm';
//up
int tx = x - 1, ty = y;
if(in(tx, ty) && !vis[tx][ty] && dfs(tx, ty)) return true;
//right
tx = x, ty = y + 1;
if(in(tx, ty) && !vis[tx][ty] && dfs(tx, ty)) return true;
//down
tx = x + 1, ty = y;
if(in(tx, ty) && !vis[tx][ty] && dfs(tx, ty)) return true;
//left
tx = x, ty = y -1;
if(in(tx, ty) && !vis[tx][ty] && dfs(tx, ty)) return true;
//not find
vis[x][y] = 0;
maze[x][y] = '.';
return false;
}
简便算法
bool dfs(int x, int y) {
if(maze[x][y] == 'T') return true;
vis[x][y] = 1;
maze[x][y] = 'm';
for(int i = 0; i < 4; i++){
int tx = x + dir[i][0];
int ty = y + dir[i][1];
if(in(tx, ty) && !vis[tx][ty] && dfs(tx, ty)) return true;
}
//not find
vis[x][y] = 0;
maze[x][y] = '.';
return false;
}
计算走的步数
void dfs(int x, int y, int step) {
// 添加剪枝约束
if(step > ans) return;
if(maze[x][y] == 'T') {
ans = step;
return;
}
vis[x][y] = 1;
for(int i = 0; i < 4; i++){
int tx = x + dir[i][0];
int ty = y + dir[i][1];
if(in(tx, ty) && !vis[tx][ty] )
dfs(tx, ty, step + 1);
}
//not find
vis[x][y] = 0;
}
问题描述
在m * n
的图中,用#
表示可以走到,用.
表示不可以走到
在图中寻找最大的#连通块
5 6
.#....
..#...
..#..#
...##.
.#....
算法思路:
1、在图中进行遍历,找到当前符号为#,并且没有访问的,初始化计数变量,进行遍历。
如果最终结果大于已知计数个数,则更新结果
2、遍历算法
如果当前结点不合法(不在图中、结点不是#、已经被访问),return结束
标记当前结点已经访问
增加连通个数
从上下左右四个方向进行遍历
#include
#include
#include
using namespace std;
#define MAXN 110
char maze[MAXN][MAXN];
bool vis[MAXN][MAXN];
int m, n, ans = 0, cnt, sx, sy;
bool in(int x, int y) {
return x >= 0 && x < m && y >= 0 && y < n;
}
void dfs(int x, int y) {
if(!in(x, y) || maze[x][y] != '#' || vis[x][y]) return;
vis[x][y] = 1;
cnt++;
dfs(x, y + 1);
dfs(x + 1, y);
dfs(x, y - 1);
dfs(x - 1, y);
}
int main()
{
cin >> m >> n;
for(int i = 0; i < m; i++) {
cin >> maze[i];
}
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
if(!vis[i][j] && maze[i][j] == '#') {
cnt = 0;
dfs(i, j);
if(cnt > ans) ans = cnt;
}
}
}
cout << ans << endl;
system("pause");
return 0;
}
计算从1,2,3,…,30这些数中选出8个数并且和为200的组合个数
#include
#include
#include
using namespace std;
#define MAXN 30
int sum = 200;
int arr[40];
int n = 30;
int k = 8;
int ans = 0;
int sel[40];
void dfs(int s, int cnt, int pos) {
if(s > sum || cnt > k) return;
if(s == sum && cnt == k) {
ans++;
return;
}
for(int i = pos; i < n; i++) {
if(!sel[i]) {
sel[i] = 1;
dfs(s + arr[i], cnt + 1, i + 1);
sel[i] = 0;
}
}
}
int main()
{
for(int i = 0; i < n; i++) {
arr[i] = i + 1;
}
memset(sel, 0, sizeof(sel));
dfs(0, 0, 0);
cout << ans << endl;
system("pause");
return 0;
}
普通算法:时间复杂度为O(n2)
void dfs(int s, int cnt, int pos) {
if(s == sum && cnt == k) {
ans++;
return;
}
for(int i = pos; i < n; i++) {
if(!sel[i]) {
sel[i] = 1;
dfs(s + arr[i], cnt + 1, i + 1);
sel[i] = 0;
}
}
}
剪枝策略:时间复杂度为O(n)
void dfs(int s, int cnt, int pos) {
if(s > sum || cnt > k) return; //剪枝语句
if(s == sum && cnt == k) {
ans++;
return;
}
for(int i = pos; i < n; i++) {
if(!sel[i]) {
sel[i] = 1;
dfs(s + arr[i], cnt + 1, i + 1);
sel[i] = 0;
}
}
}
在一个
n*m
图中将该位置的横坐标和纵坐标求和,如果是偶数就标记白色,如果是偶数就标记为黑色
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7Loa6MW8-1605282562792)(E:\images\markdown\white_black_map.png)]
上图中相邻两个格子的颜色不同,表示每走一步颜色就会不一样。更为普遍的结论是:走奇数步会改变颜色,走偶数步颜色不变。
题目描述
在一个n×m
大小的迷宫中,其中字符S
表示起点,字符D
表示出口,字符X
表示墙壁,字符.
表示空地,你需要从S
出发走到D
,每次只能向上下左右相邻的位置移动,并且不能走出地图,不能走进墙壁。
每次移动小号1时间,走过的路都会塌陷,因此不能回头或着原地不动。现在已知出口的大门会在T时间内打开,判断在0时间从起点是否能够逃离迷宫
例子
4 4 5
S.X.
..X.
..XD
....
#include
#include
using namespace std;
const int MAXN = 11;
char maze[MAXN][MAXN];
int vis[MAXN][MAXN];
bool OK;
int T, n, m, sx, sy, ex, ey;
int dir[4][2] = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
void dfs(int x, int y, int t) {
if(OK) return;
if(t > T) return; //overtime
if(t == T && maze[x][y] == 'D') { //can be find
OK = true;
return;
}
vis[x][y] = true;
maze[x][y] = '#';
for(int i = 0; i < 4; i++) {
int tx, ty;
tx = x + dir[i][0], ty = y + dir[i][1];
if(tx < 0 || tx >= n || ty < 0 || ty >= m || maze[tx][ty] == 'X' || vis[tx][ty])
continue; //notice the use of "continue" here
dfs(tx, ty, t + 1);
}
vis[x][y] = false;
maze[x][y] = '.';
}
int main()
{
cin >> n >> m >> T;
for(int i = 0; i < n; i++)
cin >> maze[i];
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
if(maze[i][j] == 'S')
sx = i, sy = j;
if(maze[i][j] == 'D')
ex = i, ey = j;
}
}
//sx + sy表示开始位置的颜色,ex + ey表示结束位置的颜色,T表示需要走多少步
//开始和结束颜色相同,T为偶数,开始和结束颜色不同,T为奇数,和总为偶数
//此种情况考虑的是必定不能达到的情况,但是不能排除时间多出的情况
if((sx + sy + ex + ey + T) % 2 == 0)
cout << "NO" << endl;
else {
OK = false;
dfs(0, 0, 0);
if(OK)
cout << "YES" << endl;
else cout << "NO" << endl;
}
for(int i = 0; i < n; i++)
cout << maze[i] << endl;
cout << endl;
system("pause");
return 0;
}
问题描述
在一个n×m
的方格地图上,某些方格上放置着炸弹。手动引爆一个炸弹后,炸弹会把炸弹所在的行和列上的所有炸弹引爆,被引爆的炸弹又能引爆其他炸弹,这样连锁下去。
现在为了引爆地图上所有的炸弹,需要手动引爆其中一些炸弹,请算出最少需要手动引爆多少个炸弹可以把地图上的所有炸弹都引爆。
5 5
00010
00010
01001
10001
01000
算法思路
0、初始化设定一个行标记数组和列标记数组,用来记录当前行和当前列是否被访问过。
1、遍历表格,找到有炸弹的位置,进行连续爆破(符合规定则爆破),统计开始爆破的个数
2、标记当前位置没有炸弹
如果当前行没有被爆破,则依次遍历当前行各个位置,如果有炸弹则进行爆破
如果当前列没有被爆破,则依次遍历当前列各个位置,如果有炸弹则爆破
#include
#include
using namespace std;
const int MAXN = 1010;
char maze[MAXN][MAXN];
int n, m;
bool row[MAXN], col[MAXN];
void boom(int x, int y) {
maze[x][y] = '0';
if(!row[x]) {
row[x] = true;
for(int i = 0; i < m; i++) {
if(maze[x][i] == '1') {
boom(x, i);
}
}
}
if(!col[y]) {
col[y] = true;
for(int i = 0; i < n; i++) {
if(maze[i][y] == '1') {
boom(i, y);
}
}
}
}
int main()
{
cin >> n >> m;
for(int i = 0; i < n; i++) {
cin >> maze[i];
}
int cnt = 0;
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
if(maze[i][j] == '1') {
++cnt;
boom(i, j);
}
}
}
cout << cnt << endl;
cout << endl;
system("pause");
return 0;
}
制作一个体积为nπ的m层蛋糕。蛋糕的每一层都是圆柱体,设从下往上数第i(0≤i
要尽可能的保证蛋糕的表面积最小,希望蛋糕外表面(最下面一层的下底除外)的面积Q最小。另Q = Sπ,请编程对给定的 n 和 m,找出蛋糕的制作方案(适当的Ri和Hi的值),使S最小。(除Q外,以上所有数据都是正整数)
思路:
1、蛋糕的基本体积和表面积求法
n = ∑ i = o m − 1 R i 2 H i 表 面 积 S = R 0 2 + 2 ∑ i = 0 m − 1 R i H i n = \sum_{i=o}^{m-1}R_i^2H_i\ \ \ \ \ \ \ \ \ 表面积S=R_0^2 + 2\sum_{i=0}^{m-1}R_iH_i n=i=o∑m−1Ri2Hi 表面积S=R02+2i=0∑m−1RiHi
因为每个圆柱体的高和半径都是正整数,所以不能用数学方法直接求出最值,只能通过DFS方法搜索去寻找答案
容易发现,除了第0层外,在体积一定的情况下,Ri越大,表面积越大。因此,在搜索的过程中,会从小到大枚举Ri,更加便于去剪枝
现在考虑第i层时,Ri和Hi的枚举范围。
按照题目要求 R i < R i − 1 R_i < R_{i-1} Ri<Ri−1,而Ri必须为正整数,那么因为 R m − 1 ≥ 1 R_{m-1} \geq 1 Rm−1≥1,可以推出 R i ≥ m − i R_i \geq m - i Ri≥m−i
因此当i > 0 时, R i ϵ [ m − i , R i − 1 − 1 ] R_i \epsilon [m-i, R_{i-1} - 1] Riϵ[m−i,Ri−1−1],同理, H i ϵ [ m − i , R i − 1 − 1 ] H_i \epsilon [m-i, R_{i-1} - 1] Hiϵ[m−i,Ri−1−1]
最底层 R 0 ≤ n , H 0 ≤ n R_0 \leq \sqrt{n}, H_0 \leq n R0≤n,H0≤n
可行性剪枝
有时候,当前情况的体积太大,导致即使后面几层的体积取到最小,也无法使最终的体积等于n。
新申请一个数组va
预处理从上往下数前i个圆柱体最小的体积和,显然va[i]
等于 ∑ j = 1 i j 3 \sum_{j=1}^i j^3 ∑j=1ij3。那么在搜索中,就可以利用这个va
数组进行可行性剪枝
最优性剪枝
如果已经搜索到一个答案ans
,而当前表面积为s,当前体积为v,若s + x ≥ ans,就能进行剪枝
其中x为第i层到第m - 1层的最小面积,我们在没搜索之前是无法计算出这个x,但是能根据当前局面估计出一个值y,满足y≤ x。我们在s + y ≥ ans 时进行剪枝,显然y越接近x,剪枝效果越好
下面通过不等式的缩放来找到这个y:
当前在第i 层,因为
#include
#include
#include
using namespace std;
const int INF = 2e9;
int n, m, ans;
int va[20];
//u表示当前层数,v表示当前体积,s表示当前表面积
//r0表示该层圆柱体半径的上界,h0表示该层圆柱体的高的上界
//当搜索完m层时,判断当前体积是否等于n,然后进行更新ans,最后return
void dfs(int u, int v, int s, int r0, int h0) {
if(u == m) {
if(v == n) {
ans = min(ans, s);
}
return;
}
if(va[m - u] + v > n) return;
if(2.0 * (n - v) / r0 + s > ans) return;
for(int r = r0; r >= m - u; r--) {
for(int h = h0; h >= m - u; h--) {
int tv = v + r * r * h;
if(tv > n) continue;
int ts = s + 2 * r * h;
if(u == 0) ts += r * r;
dfs(u + 1, tv, ts, r - 1, h - 1);
}
}
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= m; i++) {
va[i] = va[i - 1] + i * i * i;
}
int r0 = sqrt(n) + 0.5;
ans = INF;
dfs(0, 0, 0, r0, n);
if(ans == INF) ans = 0;
cout << ans << endl;
cout << endl;
system("pause");
return 0;
}
#### 找数字
给出一个数n,让你找出一个只由0,1组成的十进制数m,要求这个正整数m可以被n整除
输入一个整数n(1≤ n < 20)
输出
对于输入整数n的每一个值,输出m的相应值,保证有一个数字长度小于19位的数字。如果有一个给定的数字n有多个解,其中任何一个都是可接受的。
#include
#include
using namespace std;
int n;
int ans;
bool ok = false;
void dfs(long long m, int len) {
if(ok) return;
if(len >= 19) return;
if(m % n == 0) {
ok = true;
cout << m << endl;
return;
}
dfs(m * 10 + 0, len + 1);
dfs(m * 10 + 1, len + 1);
}
int main()
{
cin >> n;
dfs(1, 0);
system("pause");
return 0;
}
从n个不同元素中任取m(m≤n)个元素,按照一定的顺序进行排列,叫做从n个不同元素中取出m个元素的一个排列。当m=n时,所有的排列情况叫做全排列。
给一个整数n,计算[1,n]所有数字的排列组合
输入一个整数n
输出:
输出第一行为全排列的总个数
下面依次输出全排列
#include
#include
using namespace std;
int n;
bool vis[20];
void dfs(int cnt, int num) {
if(cnt == n) {
cout << num << endl;
return;
}
for(int i = 1; i <= n; i++) {
if(!vis[i]) {
vis[i] = true;
dfs(cnt + 1, num * 10 + i);
vis[i] = false;
}
}
}
int main()
{
cin >> n;
int ans = 1;
for(int i = 1; i <= n; i++) {
ans *= i;
}
cout << ans << endl;
dfs(0, 0);
system("pause");
return 0;
}
你打算旅游n个城市,但是并不是每条路线的花费都是一样的。求出把n个 城市全部旅游一遍后的最小花费
输入一个整数n
接下来又n×n的矩阵,表示每两个城市之间交通花费,每两个城市之间的花费不会超过10000
输出一个整数,表示从1号城市把所有城市旅游遍后回到1号城市的最小花费。
4
0 1 1 1
1 0 2 1
5 5 0 6
1 1 3 0
----
8
#include
#include
using namespace std;
int G[20][20];
bool vis[20];
int ans = 1e9;
int n;
void dfs(int u, int cnt, int sum) {
if(sum > ans) {
return;
}
if(cnt == n) {
ans = min(ans, sum + G[u][1]);
return;
}
vis[u] = 1;
for(int i = 1; i <= n; i++) {
if(!vis[i]) {
dfs(i, cnt + 1, sum + G[u][i]); //不能对sum进行更改
}
}
vis[u] = 0;
}
int main()
{
cin >> n;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
cin >> G[i][j];
}
}
dfs(1, 1, 0);
cout << ans << endl;
system("pause");
return 0;
}
用一些长短不一的木棍,拼成一个正方形,并且每根木棍都要用到。例如,有长度为1、2、3、3、3的5根木棍。可以让长度为1、2的木棍拼成一条边,另外三条拼成一条边。计算能否拼成一个正方形。
输入
一个整数n(4≤n≤20),表示i木棍的数量,接下来输入n根木棍的长度pi(1~10000)
输出格式
如果能拼成正方形,则输出Yes,否则输出No
4
1111
----
Yes
5
10 20 30 40 50
-----
No
#include
#include
using namespace std;
int n;
int sum = 0; //计算周长
int s1; //存储每个边的长度
int len[4] = {0}; //存储每条边的长度
int a[21] = {0}, b[21] = {0}; //存储每根木棍的长度,和没根木根是否被用过
bool ok = false; //判断是否存在解
//进行第x个边的深搜,从第j根木棍开始
void dfs(int x, int j) {
if(x == 4) { //搜索完成
ok = 1;
return;
}
if(ok) return;
if(len[x] > s1) {
return;
}
if(len[x] == s1) { //搜索到一条边,继续搜下一条边
dfs(x + 1, j);
}
else {
for(int i = j; i <= n; i++) {
if(b[i] == 0) {
b[i] = 1;
len[x] += a[i];
dfs(x, i + 1);
len[x] -= a[i];
b[i] = 0;
}
}
}
}
int main()
{
cin >> n;
for(int i = 0; i < n; i++) {
cin >> a[i];
sum += a[i];
}
s1 = sum / 4; //算出目标长度
if(sum % 4) cout << "No"; //不能均分成四条边
else {
dfs(1, 1); //从第一位开始,求出第一条边
if(ok) cout << "Yes";
else cout << "No";
}
system("pause");
return 0;
}
从1到n的范围内,因数个数最多的数是多少。如果有多个这样的数,取最小的那个。
输入:
输入整数T,表示数据的组数
接下来T行,每一行一个正整数n
输出
一共T行,每行一个整数表示最多因数个数的那个数
3
10
100
1000
----
6
60
840
#include
#include
using namespace std;
typedef long long LL;
LL ans, mc, n;
const int prime[15] = {2, 3, 5, 7, 11, 13, 19, 23, 29, 31, 37, 41, 43, 47};
//u表示第多少个素数,m表示最高多少次,x表示当前数,cnt表示因子个数
void dfs(int u, int m, LL x, LL cnt) {
if(cnt > mc) { //最多因子个数更新
mc = cnt;
ans = x;
}
else if(cnt == mc && x < ans) { //最小答案更新
ans = x;
}
if(u == 15) return; //超界
for(int i = 1; i <= m; i++) { //每个试m遍
x *= prime[u];
if(x > n) break;
dfs(u + 1, i, x, cnt * (i + 1));
}
}
int main()
{
int T;
cin >> T;
for(int i = 0; i < T; i++) {
cin >> n;
mc = 0;
dfs(0, 60, 1, 1);
cout << ans << endl;
}
system("pause");
return 0;
}
原本记录着从1~n的序列,每个数字中间有一个空格,现在将空格删除,整个序列变成一个从1~100的且首部没有空格的数字串
请编写程序将原来的数据复原
输入:
输入文件有一行,为一个字符串
字符串的长度在1~100之间
输出:
输出共一行,为原数据:1~n的输出,任意两个数据中间用空格隔开
如果有多组,输出其中任意一组
#include
#include
using namespace std;
string s; //输入的字符串
int ans[100]; //存放最终的结果
int n; //存放最多含有的数的个数
bool vis[100]; //存放当前数是否被用到
bool ok; //存放是否成功跟组
void dfs(int u, int cnt) //遍历从第 u 位开始,第 cnt 个数
{
if(ok) return; //success
if(u == s.size()) { //长度正好,输出这些数
for(int i = 0; i < cnt; i++) {
cout << ans[i] << " ";
}
cout << endl;
ok = true;
return;
}
int x = s[u] - '0'; //拆出一位
if(u + 1 >= s.size()) return; //长度大于1 结束
x = x * 10 + s[u + 1] - '0'; //两位数
if(x <= n && !vis[x]) { //如果x在这个范围最大的序列中,并且没有被取到
ans[cnt] = x; //记录这一位的答案
vis[x] = true; //标记这个数被用到
dfs(u + 2, cnt + 1); //找下一个数
vis[x] = false;
}
}
int main()
{
cin >> s;
//对于最多含有数的个数,存在1-9都在里面,也就是说顺序的情况下前9个数单位数,后面都是两位的
n = s.size() <= 9 ? s.size() : (s.size() - 9) / 2 + 9;
dfs(0, 0);
system("pause");
return 0;
}
一个正方形的镇区分为N*N(1≤N≤7)个方块。农场位于方格的左上角。
Betsy穿过小镇,从左上角走到最下角,刚好经过每个小方格一次。
当N=3时,Betsy的漫游路径可能如下图所示:
----------------
| | | |
| F********** |
| | | * |
------------*---
| | | * |
| ***** | * |
| * | * | * |
| * | * | * |
| * | * | * |
| M | ****** |
| | | |
----------------
求出最终的路径数
输入
输入一行一个整数N(1≤N≤7)
输出格式
输出一行表示不同的路径数
3
---
2
#include
#include
#include
using namespace std;
int vis[10][10]; //表示当前位置是否被访问
int last[10][10]; //表示当前格子周围有多少个格子未被访问(最多有4个)
int dx[4] = {0, 0, 1, -1}; //x轴变化
int dy[4] = {1, -1, 0, 0}; //y轴变化
int n, ans; //存放n*n的方格,和答案ans
bool in(int x, int y) { //当前位置合法
return x >= 1 && x <= n && y >= 1 && y <= n;
}
void dfs(int x, int y, int cnt) { //遍历第[x, y] 个位置,每次遍历包含一进一出
if(x == n && y == n) {
if(cnt == n * n) ++ans;
return;
}
int dir = -1;
vis[x][y] = true;
int tx, ty;
for(int i = 0 ; i < 4; i++) {
tx += dx[i], ty += dy[i];
if(in(tx, ty) && !vis[tx][ty]) {
last[tx][ty]--;
if(last[tx][ty] == 1) { //如果当前位置周围未被访问元素只剩下1的时候,只能访问当前位置
dir = i;
}
}
}
for(int i = 0; i < 4; i++) {
if(dir != -1 && dir != i) continue;
tx += dx[i], ty += dy[i];
if(in(tx, ty) && !vis[tx][ty]) {
bool ok = true;
int r = 0;
for(int j = 0; j < 4; j++) {
int ex = tx + dx[j], ey = ty + dy[j];
if(in(ex, ey) && !vis[ex][ey]) {
if(last[ex][ey] < 2) {
ok = false;
break;
}
else if(last[ex][ey] == 2) {
r++;
}
}
}
if(ok && r <= 1) dfs(tx, ty, cnt + 1);
}
}
//将当前结点重置
vis[x][y] = false;
for(int i = 0; i < 4; i++) {
int ex = x + dx[i];
int ey = y + dy[i];
if(in(ex, ey) && !vis[ex][ey]) last[ex][ey]++;
}
}
int main()
{
cin >> n;
for(int i = 1; i <= n; ++i) {
for(int j = 0; j <= n; ++j) {
for(int k = 0; k < 4; k++) {
int tx = i + dx[k];
int ty = j + dy[k];
if(in(tx, ty)) {
++last[i][j];
}
}
}
}
last[n][1]++;
ans = 0;
dfs(1, 1, 1);
cout << ans << endl;
system("pause");
return 0;
}
图的表示方法:邻接矩阵和邻接表
邻接表的优点:
1.节省空间(邻接矩阵记录的是是否连通);
2.可以记录重复边(两个顶点间有多个边)
邻接表的缺点:随机访问效率低。
表示方式:
邻接矩阵:int G[N][N]; or bool G[N][N];
邻接表:vector
; 存储的是连接的顶点,记录有向图;
带权图的表示方式:
邻接矩阵:int G[N][N];
邻接表:
typedef struct Node {
int v; //记录连接的点
int w; //记录权制
}
vector<node> G[N];
//插入一条有向边
void insert(int u, int v, int w) {
node temp;
temp.v = v;
temp.w = w;
}
//插入一条无向边,等价于插入两条方向相反的有向边
void insertPlus(int u, int v, int w) {
insert(u, v, w);
insert(v, u, w);
}
题目描述
输入n对朋友关系,朋友关系是相互的,a是b的朋友,b也是a的朋友。
有m次查询,每次查询a和b是否是朋友
5
Mary Tom
Islans Barty
Andy Amy
Islans Amy
Tom Mary
3
Amy Andy
Islands Tom
Islands Barty
map
库
#include
#include
#include
#include
using namespace std;
int G[500][500];
map<string, int> dict; //定义的是一个字符串向整数映射的哈希映射表
int ids; //定义一个身份变量
//用来查找当前字符串是否在这个映射中出现
int find(string a) {
if(dict.find(a) == dict.end()) dict[a] = ++ids; //or:: if(dict.count(a) == 0)
return dict[a];
}
int main() {
memset(G, 0, sizeof(G));
int n;
cin >> n;
for(int i = 0; i < n; i++) {
string a, b;
cin >> a >> b;
int x = find(a), y = find(b);
G[x][y] = G[y][x] = 1;
}
int m;
cin >> m;
for(int i = 0; i < m; i++) {
string a, b;
cin >> a >> b;
int x = find(a), y = find(b);
if(G[x][y]) cout << "YES" << endl;
else cout << "NO" << endl;
}
system("pause");
return 0;
}
在给定n个城市中,从开始的城市选择出最短的路径到达终点
#include
#include
#include
using namespace std;
int vis[10][10]; //表示当前位置是否被访问
int last[10][10]; //表示当前格子周围有多少个格子未被访问(最多有4个)
int dx[4] = {0, 0, 1, -1}; //x轴变化
int dy[4] = {1, -1, 0, 0}; //y轴变化
int n, ans; //存放n*n的方格,和答案ans
bool in(int x, int y) { //当前位置合法
return x >= 1 && x <= n && y >= 1 && y <= n;
}
void dfs(int x, int y, int cnt) { //遍历第[x, y] 个位置,每次遍历包含一进一出
if(x == n && y == n) {
if(cnt == n * n) ++ans;
return;
}
int dir = -1;
vis[x][y] = true;
int tx, ty;
for(int i = 0 ; i < 4; i++) {
tx += dx[i], ty += dy[i];
if(in(tx, ty) && !vis[tx][ty]) {
last[tx][ty]--;
if(last[tx][ty] == 1) { //如果当前位置周围未被访问元素只剩下1的时候,只能访问当前位置
dir = i;
}
}
}
for(int i = 0; i < 4; i++) {
if(dir != -1 && dir != i) continue;
tx += dx[i], ty += dy[i];
if(in(tx, ty) && !vis[tx][ty]) {
bool ok = true;
int r = 0;
for(int j = 0; j < 4; j++) {
int ex = tx + dx[j], ey = ty + dy[j];
if(in(ex, ey) && !vis[ex][ey]) {
if(last[ex][ey] < 2) {
ok = false;
break;
}
else if(last[ex][ey] == 2) {
r++;
}
}
}
if(ok && r <= 1) dfs(tx, ty, cnt + 1);
}
}
//将当前结点重置
vis[x][y] = false;
for(int i = 0; i < 4; i++) {
int ex = x + dx[i];
int ey = y + dy[i];
if(in(ex, ey) && !vis[ex][ey]) last[ex][ey]++;
}
}
int main()
{
cin >> n;
for(int i = 1; i <= n; ++i) {
for(int j = 0; j <= n; ++j) {
for(int k = 0; k < 4; k++) {
int tx = i + dx[k];
int ty = j + dy[k];
if(in(tx, ty)) {
++last[i][j];
}
}
}
}
last[n][1]++;
ans = 0;
dfs(1, 1, 1);
cout << ans << endl;
system("pause");
return 0;
}
二进制运算:&
, l
, ^
, >>
, <<
(按位与,按位或,按位异或,按位左移,按位右移)
按位左、右移可以进行乘/除2运算,如表示2的10次方:1<<10
可以用按位与运算来判断当前位置是否需要选中
遇店加倍,遇花减一。
共遇到5次店,10次花,最后一次遇到的是花,正好将酒喝完。
一共是14次机会,如果用1表示遇到店,用0表示遇到花,则一共可以用14位二进制来表示这个枚举个数。
int ans;
for(int i = 0; i < (1 << 14); i++) {
int tot1 = 0;
int tot0 = 0;
int num = 2; //开局两单位酒
for(int j = 0; j < 14; j++) {
if(i & (1 << j)) { //这里表示取出第j位并判断是否是1
tot1++;
num *= 2;
}
else{
tot0++;
num = num - 1;
}
}
if(tot1 == 5 && tot0 == 9 && num == 1) {
++ans; //记录合法方案数
}
}
在这样的情况下:如果要选择n个元素,每个元素有两种情况:选择和不选择,即两种状态。当要表达取出这些元素的全部情况时,需要创建一个n维的数组,但是这样会占用大量的空间。这时候就需要压缩状态,即用一个数来表示,这个数的二进制的第i位表示第i个元素的选择状态。
问题描述
n个人传递物品,编号为1~n
游戏规则为:开始时物品可以在任意一人手上,他可以把物品传递给其他人中的任意一位;下一个人可以传递给未接过物品的任意一人。
即物品只能经过同一人一次,而且每次传递过程都有一个代价;不同的人传递给不同的人的代价之间是没有联系。
求当物品经过所有人传递过后,整个过程的总代价是多少?
解析
将“当前传递过物品的人的集合、最后一个传递到的人”作为状态进行动态规划,用dp[i][j]
表示这个状态的最小代价。
这里需要使用状态压缩,把“传递过物品的人的集合”压缩为一个整数。我们用一个二进制数表示这个集合。
3
-1 2 4
3 -1 5
4 4 -1
------
6
#include
#include
#include
#include
using namespace std;
const int INF = 0x3f3f3f3f;
int a[20][20] ;
int dp[1 << 16][20];
int main()
{
int n;
cin >> n;
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
cin >> a[i][j];
}
}
memset(dp, 0x3f, sizeof(dp)); //初始化动态数组全部为一个极大值
for(int i = 0; i < n; i++) { //初始化自己传递给自己的值为 0
dp[1 << i][i] = 0;
}
for(int i = 0; i < (1 << n); i++) {
for(int j = 0; j < n; j++) {
if(i & (1 << j)) {
for(int k = 0; k < n; k++) {
if(!(i & (1 << k))) {
dp[i | 1 << k][k] = min(dp[i | 1 << k][k], dp[i][j] + a[j][k]);
}
}
}
}
}
int ans = INF;
for(int i = 0; i < n; i++) {
ans = min(ans, dp[(1 << n) - 1][i]);
}
cout << ans << endl;
system("pause");
return 0;
}
TSP问题(Travelling Saleman Peoblem)
TSP问题是一个经典的NP-完全问题
假设有一个旅行商人要拜访n个城市,他必须选择索要走的路径,路径的限制是每个城市只能拜访一次,而且最后要回到原来出发的城市。路径的选择目标是要求所得路径的路程是所有路径之中的最小值。
有n个城市,两两之间均有道路直接相连。给出每个城市i和j之间的道路长度dist(i,j)
,求一条经过每个城市且仅一次,最后回到起点的路径,使得经过的道路总长度最短。n≤16,城市编号为0~n-1
因为路线是一个环,可以规定任意点为起点和终点,不妨设城市0为起点和终点。设(S,i)
表示当前城市i,已访问城市集合为S的最短长度。则 d ( S , i ) = m i n { d ( S − { i } , j ) + d i s t ( j , i ) ∣ i ϵ S } d(S,i) = min\{d(S - \{i\}, j) + dist(j, i) | i\epsilon S\} d(S,i)=min{d(S−{i},j)+dist(j,i)∣iϵS}
初始时 d ( 0 , 0 ) = 0 d(0, {0}) = 0 d(0,0)=0,最终答案为 m i n { d ( { 0 , 1 , 2 , . . . , n − 1 } , i ) + d i s t ( i , 0 ) ∣ i ≠ 0 } min\{d(\{0, 1, 2, ..., n-1\}, i) + dist(i, 0) | i\neq 0\} min{d({0,1,2,...,n−1},i)+dist(i,0)∣i=0}
时间复杂度为 O ( n 2 2 n ) O(n^22^n) O(n22n)
memset(dp, 0x3f, sizeof(dp));
do[1][0] = 0;
for(int s = 0; s < (1 << n); s++) {
for(int i = 0; i < n; i++) {
if(s & (1 << i)) {
for(int j = 0; j < n; j++) {
if(j != i && (s & (1 << j))) {
dp[s][i] = min(dp[s][i], dp[s ^ 1 << i][j] + dist[j][i]);
}
}
}
}
}
现在修改TSP问题,去掉每个城市只能拜访一次的限制。这样,从城市i到城市j的时候,最短路径可能经过已经经过的点。这时候,只需要走最短路径即可。首先需要用Floyd
算法预处理出每两个城市之间的最短路。这一步称为闭包传递
3
-1 1 10
1 -1 2
10 2 -1
---------
6
#include
#include
#include
#include
using namespace std;
const int INF = 0x3f3f3f3f;
int dist[20][20];
int dp[1 << 16][20];
int main()
{
int n;
cin >> n;
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
cin >> dist[i][j];
if(dist[i][j] == -1) dist[i][j] = INF;
}
}
//floyd算法
for(int k = 0; k < n; k++) {
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
}
}
}
memset(dp, 0x3f, sizeof(dp));
dp[1][0] = 0;
for(int s = 0; s < (1 << n); s++) {
for(int i = 0 ; i < n; i++) { //求得i为终点的最短路径
if(s & (1 << i)) { //i城市被访问过,i位置上为1
for(int j = 0; j < n; j++) {
if(j != i && (s & (1 << j))) { //j城市被访问过,j位置上为1
dp[s][i] = min(dp[s][i], dp[s ^ 1 << i][j] + dist[j][i]);
}//i,j城市已经被访问,在j为最终位置,并且没有访问i城市基础上,连接i和j并求得最小值
}
}
}
}
int ans = INF;
for(int i = 0; i < n; i++) {
ans = min(ans, dp[(1 << n) - 1][i] + dist[i][0]);
}
if(ans == INF) ans = -1;
cout << ans << endl;
system("pause");
return 0;
}
给定一个n×m的矩阵,行数和列数都不超过20,其中有些格子可以选,有些格子不能选。现在需要从中选择尽可能多的格子,且保证选出的所有格子之间不相邻
111
010
---
x1x
0x0
1111
1111
1111
通过状态压缩DP来解决方格取数问题
我们可以自上而下,一行一行地选择格子。在一行内选择格子的时候,只和上一行的选择方式有关,我们就可以将“当前放到第几行、当前行的选择方案”作为状态进行动态规划
这里,需要用到状态压缩:一行里被选择的格子实际上是一个集合,我们要将这个集合压缩为一个整数。比如,对于一个3列的矩阵,如果当前行的状态是(5)10=(101)2,那么就意味着当前行选择了第一个和第三个。
如果上一行的状态是now
,下一行的状态是prev
,那么我们只需要确保上下两行的选择方案里没有重复的元素,也就是(now & prev) == 0
就可以
此外,还需要判断当前行状态是否合法,因为读入的矩阵并不是每个格子都可以选择的,如果我们将矩阵中的每行值也用状态压缩来存储,不妨记作flag
,那么当前选择的格子的集合一定 包含于 当前合法格子的集合,即(now | flag) == flag
2 3
1 1 1
0 1 0
-----
3
#include
#include
using namespace std;
int a[21][20];
int state[21];
int dp[21][1 << 20];
bool ok(int now) {
return (now & (now >> 1)) == 0;
}
bool fit(int now, int i) {
return (now | state[i]) == state[i];
}
bool not_intersect(int now, int prev) {
return (now & prev) == 0;
}
int count(int now) {
int s = 0;
while(now) {
s += (now & 1);
now >>= 1;
}
return s;
}
int main()
{
int n, m;
cin >> n >> m;
//行从 1 开始,列从 0 开始,这样后面处理起来会更加方便
for(int i = 1; i <= n; i++) {
for(int j = 0; j < m; j++) {
cin >> a[i][j];
}
}
for(int i = 1; i <= n; i++) {
for(int j = 0; j < m; j++) {
if(a[i][j]) {
state[i] += (1 << j);
}
}
}
for(int i = 1; i <= n; i++) {
for(int j = 0; j < (1 << m); j++) {
if(ok(j) && fit(j, i)) {
for(int k = 0; k < (1 << m); k++) {
if(ok(k) && fit(k, i - 1) && not_intersect(j, k)) {
dp[i][j] = max(dp[i][j], dp[i - 1][k] + count(j));
}
}
}
}
}
int ans = 0;
for (int i = 0; i < (1 << m); i++) {
ans = max(ans, dp[n][i]);
}
cout << ans << endl;
system("pause");
return 0;
}
#### 取模运算 %
(a + b) % n = (a % n + b % n) % n
(a * b) % n = (a % n * b % n) % n
最大公约数: gcd(greatest common divisor)
最小公倍数: lcm(lowest common multiple)
最小公倍数 = a × b g c d ( a , b ) \frac{a \times b}{gcd(a, b)} gcd(a,b)a×b
欧几里得算法又称为辗转相除法。该算法用来快速计算2个整数的最大公约数。
欧几里得算法的原理就是一个公示:gcd(a, b) = gcd(b, a mod b)
int gcd(int a, int b) {
if(b == a) return a;
return gcd(b, a % b);
}
注:在头文件#include
中有求最大公约数的函数:__gcd(a, b)
,注意gcd
前有两道下划线__
C++ std::map 的使用
map是一个关联容器,里面存放的是键值对:key value
1、定义与特性
所在头文件
std::map 类模板:
template < class Key, // map::key_type
class T, // map::mapped_type
class Compare = less<Key>, // map::key_compare
class Alloc = allocator<pair<const Key,T> > // map::allocator_type
> class map;
在std::map中,key和T一起组成std::map的value_type:
typedef pair
类型定义
类型成员 | 定义 |
---|---|
key_type | 第一个模板参数(Key) |
mapped_type | 第二个模板参数(T) |
value_type | pair |
key_compare | 第三个模板参数(Compare) |
关联性:std::map 是一个关联容器,其中的元素根据键来引用,而不是根据索引来引用。
有序性:在内部,std::map 中的元素总是按照其内部的比较器(比较器类型由Compare类型参数指定)指示的特定严格弱序标准按其键排序。
唯一性:std::map 中的元素的键是唯一的。
std::map 通常由二叉搜索树实现。
构造方式 | 函数声明 |
---|---|
构造空map | explicit map (const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type()); explicit map (const allocator_type& alloc); |
由一对范围迭代器指定输入 | template map (InputIterator first, InputIterator last, const key_compare& comp = key_compare(), const allocator_type& = allocator_type()); |
复制构造 | map (const map& x); map (const map& x, const allocator_type& alloc); |
移动构造 | map (map&& x); map (map&& x, const allocator_type& alloc); |
利用初始化列表构造 | map (initializer_list |
// constructing maps
#include
#include
// 比较器1
bool fncomp (char lhs, char rhs) {
return lhs < rhs;
}
// 比较器2
struct classcomp {
bool operator() (const char& lhs, const char& rhs) const {
return lhs<rhs;
}
};
int main () {
// 默认构造,构造一个空的map
std::map<char,int> first;
first['a']=10;
first['b']=30;
first['c']=50;
first['d']=70;
// 由一对范围迭代器指定输入
std::map<char,int> second (first.begin(), first.end());
// 复制构造
std::map<char,int> third (second);
// 指定比较器:使用类
std::map<char, int, classcomp> fourth; // class as Compare
// 指定比较器:使用函数指针
bool(*fn_pt)(char, char) = fncomp;
std::map<char, int, bool(*)(char,char)> fifth (fn_pt); // function pointer as Compare
return 0;
}
赋值方式 | 函数声明 |
---|---|
复制 | map& operator= (const map& x); |
移动 | map& operator= (map&& x); |
初始化列表 | map& operator= (initializer_list |
函数声明 | 解释 | 返回值类型 |
---|---|---|
begin() | 返回一个迭代器,指向第一个元素 | iterator 或 const_iterator |
end() | 返回一个迭代器,指向尾后元素 | iterator 或 const_iterator |
rbegin() | 返回一个反向迭代器,指向最后一个元素 | reverse_iterator 或 const_reverse_iterator |
rend() | 返回一个反向迭代器,指向第一个元素之前虚拟的元素 | reverse_iterator 或 const_reverse_iterator |
cbegin() | 返回一个常量迭代器,指向第一个元素 | const_iterator |
cend() | 返回一个常量迭代器,指向尾后元素 | const_iterator |
crbegin() | 返回一个常量反向迭代器,指向最后一个元素 | const_reverse_iterator |
crend() | 返回一个常量反向迭代器,指向第一个元素之前虚拟的元素 | const_reverse_iterator |
函数声明 | 解释 |
---|---|
bool empty() const noexcept; | map 是否为空 |
size_type size() const noexcept; | 获取map 中元素的数量 |
访问方式 | 函数声明 | 解释 |
---|---|---|
使用方括号([]) | mapped_type& operator[] (const key_type& k); mapped_type& operator[] (key_type&& k); | 如果 k 匹配容器中某个元素的键,则该函数返回该映射值的引用。 如果 k 与容器中任何元素的键都不匹配,则该函数将使用该键插入一个新元素,并返回该映射值的引用。 |
使用 at() | mapped_type& at (const key_type& k); const mapped_type& at (const key_type& k) const; | 如果 k 匹配容器中某个元素的键,则该函数返回该映射值的引用。 如果 k 与容器中任何元素的键都不匹配,则该函数将抛出 out_of_range 异常。 |
注意:const std::map 不能使用 operator[] 操作!!
插入方式 | 函数声明 | 说明 |
---|---|---|
插入单个元素 | pair |
返回一个pair,其中第一个值为一个迭代器,指向新插入的元素或其键等于待插入元素的键的元素(原先就已存在的元素);第二个值是一个bool值,当插入一个新元素时,该值设为true,当该键已存在时,该值设为false |
带插入位置提示 | iterator insert (const_iterator position, const value_type& val); template iterator insert (const_iterator position, P&& val); | 返回一个迭代器,该迭代器指向新插入的元素或指向键相等的已存在元素。 |
由一对范围迭代器指定输入 | template void insert (InputIterator first, InputIterator last); | |
使用初始化列表指定插入元素 | void insert (initializer_list |
例子:
// map::insert (C++98)
#include
#include
int main () {
std::map<char, int> mymap;
// 插入单个元素
mymap.insert ( std::pair<char,int>('a',100) );
mymap.insert ( std::pair<char,int>('z',200) );
std::pair<std::map<char,int>::iterator,bool> ret;
ret = mymap.insert ( std::pair<char,int>('z',500) );
if (ret.second==false) {
std::cout << "element 'z' already existed";
std::cout << " with a value of " << ret.first->second << '\n';
}
// 带插入位置提示,只是提示,并不强制插入此位置
std::map<char,int>::iterator it = mymap.begin();
mymap.insert (it, std::pair<char,int>('b',300)); // max efficiency inserting
mymap.insert (it, std::pair<char,int>('c',400)); // no max efficiency inserting
// 由一对范围迭代器指定输入
std::map<char,int> anothermap;
anothermap.insert(mymap.begin(), mymap.find('c'));
// 打印内容
std::cout << "mymap contains:\n";
for (it=mymap.begin(); it!=mymap.end(); ++it)
std::cout << it->first << " => " << it->second << '\n';
std::cout << "anothermap contains:\n";
for (it=anothermap.begin(); it!=anothermap.end(); ++it)
std::cout << it->first << " => " << it->second << '\n';
return 0;
}
删除方式 | 函数声明 | 说明 |
---|---|---|
根据元素位置 | iterator erase (const_iterator position); | 返回一个迭代器,指向被删除元素的后一个元素 |
根据元素的键 | size_type erase (const key_type& k); | 返回被删除元素的数目,此处为1 |
由一对范围迭代器指定删除的范围 | iterator erase (const_iterator first, const_iterator last); | 返回一个迭代器,指向最后一个被删除元素的后一个元素 |
删除所有元素 | void clear() noexcept; |
例子:
// erasing from map
#include
#include
int main () {
std::map<char,int> mymap;
std::map<char,int>::iterator it;
// insert some values:
mymap['a']=10;
mymap['b']=20;
mymap['c']=30;
mymap['d']=40;
mymap['e']=50;
mymap['f']=60;
it=mymap.find('b');
mymap.erase (it); // 删除迭代器指向的元素
mymap.erase ('c'); // 删除相应键值的元素
it=mymap.find ('e');
mymap.erase ( it, mymap.end() ); // 删除一个范围内的所有元素
// show content:
for (it=mymap.begin(); it!=mymap.end(); ++it)
std::cout << it->first << " => " << it->second << '\n';
return 0;
}
123456789101112131415161718192021222324252627282930
函数声明 | 说明 |
---|---|
iterator find (const key_type& k); const_iterator find (const key_type& k) const; | 在容器中搜索键值等于 k 的元素,如果找到,则返回一个指向该元素的迭代器,否则返回一个指向map :: end的迭代器。 |
例子:
// map::find
#include
#include
int main () {
std::map<char,int> mymap;
std::map<char,int>::iterator it;
mymap['a']=50;
mymap['b']=100;
mymap['c']=150;
mymap['d']=200;
// 查找
it = mymap.find('b');
if (it != mymap.end())
mymap.erase (it);
// print content:
std::cout << "elements in mymap:" << '\n';
std::cout << "a => " << mymap.find('a')->second << '\n';
std::cout << "c => " << mymap.find('c')->second << '\n';
std::cout << "d => " << mymap.find('d')->second << '\n';
return 0;
}
函数声明 | 说明 |
---|---|
iterator lower_bound (const key_type& k); const_iterator lower_bound (const key_type& k) const; | 返回一个迭代器,指向容器中第一个键值等于 k 或排在 k 之后的元素 |
iterator upper_bound (const key_type& k); const_iterator upper_bound (const key_type& k) const; | 返回一个迭代器,指向容器中第一个键值排在 k 之后的元素 |
pair |
返回一对迭代器,该范围内的所有元素的键值等于 k,当然,此处只包含一个元素。 如果找不到键值与 k 相等的元素,则两个迭代器均指向排在 k 之后的第一个元素。 |
例子:
// map::lower_bound/upper_bound
#include
#include
int main () {
std::map<char,int> mymap;
std::map<char,int>::iterator itlow,itup;
mymap['a']=20;
mymap['b']=40;
mymap['c']=60;
mymap['d']=80;
mymap['e']=100;
itlow=mymap.lower_bound ('b'); // itlow points to b:等于或大于'b'
itup=mymap.upper_bound ('d'); // itup points to e (not d!):大于'd'
mymap.erase(itlow,itup); // erases [itlow,itup)
// print content:
for (std::map<char,int>::iterator it=mymap.begin(); it!=mymap.end(); ++it)
std::cout << it->first << " => " << it->second << '\n';
return 0;
}
分治:将原问题划分为互不相交的子问题,递归地求解子问题,再将它们的解组合起来
动态规划:子问题重叠的情况,不同的子问题具有公共的子问题
最优子结构:问题的最优解由相关子问题的最优解组合而成
边界:维妮塔的边界,得到有限的边界
动态转移方程:问题每一阶段和下一阶段的关系
滚动数组:滚动数组是DP中的一种编程思想。简单的理解就是让数组滚动起来,每次都使用固定的几个存储空间,来达到压缩,节省存储空间的作用。
主要应用在递推或动态规划中(如01背包问题)。因为DP题目是一个自底向上的扩展过程,我们常常需要用到的是连续的解,前面的解往往可以舍去。所以用滚动数组优化是很有效的。利用滚动数组的话在N很大的情况下可以达到压缩存储的作用。
1、问题中的状态满足最优性原理——最优子结构
2、问题中的状态必须满足无后效性——以前出现状态和以前状态的变化过程不会影响将来的变化。