RE:从零开始的算法之路第二章

0.关于

有些人认为,计算机硬件已经这么先进,答案让计算机一个个去试不就能得出正确答案了吗?
没错,暴力法就是这种思想的体现,但暴力法始终是复杂度中最高的那个,我们想要对它进行优化
暴力的主要操作是搜素,主要是BFS(深度优先搜索)和DFS(广度优先搜索),一般操作是:

  1. 找到所有可能的数据,用合适的数据结构储存
  2. 尽可能多的筛选掉不合格的数据,减少搜索的空间
  3. 使用算法快速搜索

1.排列和组合

排列和递归

介绍

排列组合是暴力经常用到的,比如基础的全排列,它是用递归完成的
递归是把大问题换成性质相同的小问题,直到问题小到可以直接得出结果
由于性质相同,解决问题的程序也相同,只要不断调用自己就可以了

例题

  • 打印n个数的全排列
  • 输入
    第一行输入一个n,组成1~n个数
  • 输出
    将n个数的全排列打印出来

思路

  • 用n个for循环谁用谁sb
  • stl中有nrxt_permutation和sort可以用
  • 递归,因为全排列可以拆
    代码
    stl全排列
#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
const int maxn = 1e5 + 10;
#define re(x) for(int i=0;i<x;++i)
#define rey(x) for(int j=0;j<x;++j)
typedef long long LL;
using namespace std;
int T, n, a[maxn], m,*p,flag=1;
int main()
{
	
	ios::sync_with_stdio(0); cin.tie(0);
	while (cin>>n)
	{
		re(n)a[i] = i + 1;
		do
		{
			re(n)cout << a[i];
			cout<< "  ";
		} while (next_permutation(a,a+n));
	}
}

递归

  • 第一层:将第一个数和后面每个数互换,得到n个数列
  • 第二层:对每个数列去掉第一个数,对后面n-1个进行类似的排列
  • 第n层:最后2个数互换
#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
const int maxn = 1e5 + 10;
#define re(x) for(int i=0;i<x;++i)
#define rey(x) for(int j=0;j<x;++j)
typedef long long LL;
using namespace std;
int T, n, s[maxn], m,*p,flag=1;
int a[101];//记录排列
int vis[101];//标记数组
int tot;//排列数

void f(int k)//k为当前位置
{
	if (k == n + 1)//K=n+1说明a[n]处的元素已经选完了,此次递归可以结束,打印排列
	{
		tot++;
		for (int j = 1; j <= n; j++)
			cout << a[j] << " ";
		cout << endl;
	}

	for (int i = 1; i <= n; i++)//从1~n中选择一个数
	{
		if (vis[i] == 0)//如果没有选择过
		{
			a[k] = i;//选择它,并且放到第i位置
			vis[i] = 1;//标记为已经选择过该元素
			f(k + 1);//进行递归
			vis[i] = 0;//回溯
		}
	}
}

int main()//代码网上找的,所以看起来有点怪异
{
	
	ios::sync_with_stdio(0); cin.tie(0);
	cout << "请输入n:";
	cin >> n;
	for (int i = 1; i <= n; i++)
		vis[i] = 0;
	f(1);
	cout << "排列数为:" << tot << endl;
}

组合和子集生成

介绍

众所周知一个集合假如有n个元素,那么他的子集数量是2n
这个可以用二进制的关系来理解子集生成,如3个元素a,b,c有8个子集,从0开始到7结束对应的3位2进制数
000对应{},010对应{b},101对应{a,c},111对应{a,b,c}
在这种概念下,二进制数有多少1就是有多少元素
如何判断有多少个1呢?

  • 可以对n位二进制逐位检查,要检查n次
  • 定位1的位置跳过0,用到一个神奇的操作kk=kk&(kk-1),作用是消除最后一个1,有多少1就做多少次
  • glibc有处理2进制的内部函数,int_builtin_popcount(unsigned int x)就是返回x二进制中1的个数

例题

  • 打印n个数中任意m个数的组合(使用kk-kk&(kk-1))
#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
const int maxn = 1e5 + 10;
#define re(x) for(int i=0;i<x;++i)
#define rey(x) for(int j=0;j<x;++j)
typedef long long LL;
using namespace std;
int T, n, a[maxn], m,*p,flag=1;
void printnm(int n,int k) {
	for (int i = 0; i < (1<<n); i++)//1<<n是将1的二进制向左移n位,相当于2的n次方个(可以用函数pow(2,n))
	{
		int num = 0,kk = i;
		while (kk)//统计1的个数并放入bun里
		{
			kk = kk & (kk - 1);
			num++;
		}
		if (num == k) {//如果1的数目是k的话
			rey(n)if (i & (1 << j))//
				cout << j << " ";
			cout << endl;
		}

	}
}
int main()
{
	
	ios::sync_with_stdio(0); cin.tie(0);
	int n, k;
	cin >> n >> k;
	printnm(n, k);
}

2.BFS

BFS与队列

介绍

广度优先搜索一般以队列形式来实现,如果想让老鼠走迷宫怎么办呢?

  • 让一只老鼠走并规定顺序(如遇到分叉先左后右),遇到死路就退回到上一个节点,并将这条路标记(不会再走了),这样一只老鼠可以走完所有路最终找到出口这种思路就是DFS
  • 让无数老鼠从入口走,每个分叉就分裂将所有路都走,碰到死路就停下,看起来像"并行计算",这种思路就是BFS

例题

  • Red and Black HDU - 1312

  • There is a rectangular room, covered with square tiles. Each tile is colored either red or black. A man is standing on a black tile. From a tile, he can move to one of four adjacent tiles. But he can’t move on red tiles, he can move only on black tiles.
    Write a program to count the number of black tiles which he can reach by repeating the moves described above.

  • Input
    The input consists of multiple data sets. A data set starts with a line containing two positive integers W and H; W and H are the numbers of tiles in the x- and y- directions, respectively. W and H are not more than 20.

    There are H more lines in the data set, each of which includes W characters. Each character represents the color of a tile as follows.

‘.’ - a black tile
‘#’ - a red tile
‘@’ - a man on a black tile(appears exactly once in a data set)

  • Output
    For each data set, your program should output a line which contains the number of tiles he can reach from the initial tile (including itself).
  • Sample Input
    6 9
    …#.
    …#





    #@…#
    .#…#.
    11 9
    .#…
    .#.#######.
    .#.#…#.
    .#.#.###.#.
    .#.#…@#.#.
    .#.#####.#.
    .#…#.
    .#########.

    11 6
    …#…#…#…
    …#…#…#…
    …#…#…###
    …#…#…#@.
    …#…#…#…
    …#…#…#…
    7 7
    …#.#…
    …#.#…
    ###.###
    …@…
    ###.###
    …#.#…
    …#.#…
    0 0
  • Sample Output
    45
    59
    6
    13
    思路
    *. #代表不能走的位置,*表示可以走的位置,@表示自己位置
  1. 从起点1开始,1入队,遍历旁边的,假如23是*
  2. 1出队,2,3进队,一个个遍历出来4,5,6,7是*
  3. 2 ,3出队,4,5,6,7入队
  4. 假如后面没了,4,5,6,7出队,队空结束,入队元素的数量就死总数量
#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
#define re(x) for(int i=0;i<x;++i)
#define rey(y) for(int j=0;j<y;++j)
#define Cheak(x,y) (x<W&&x>=0&&y>=0&&y<H)//检查是否越界
typedef long long LL;
int T, n, m, a[maxn], flag = 1, dir[4][2] = { {-1,0},{0,-1},{1,0},{0,1} };//方便访问四方位置
char s[20][20];
int  H, W;
struct point
{
	int x, y;
};
int main()
{
	ios::sync_with_stdio(0); cin.tie(0);
	
	while (cin >> W >>H)
	{
		int x, y;
		if (!W &&! H)break;
		rey(H)re(W) {//先列再行,被这个坑到了
			cin >> s[i][j];
			if (s[i][j] == '@'){ //找到@位置
				x = i; 
				y = j;
			}
			
		}
		queue<point>QU;
		point next, por;
		por.x = x;
		por.y = y;
		QU.push(por);//先将@放入队列中
		int num = 1;//初始@放进去后num为1
		while (!QU.empty())//当QU空了后就说明循环所有QU内元素,没有新的.出现
		{
			por = QU.front();//por从头取一个
			QU.pop();
			re(4) {
				next.x = por.x + dir[i][0];
				next.y = por.y + dir[i][1];//快速遍历四方
				if (Cheak(next.x, next.y) && s[next.x][next.y] == '.') {
					++num;//找到.就加num
					s[next.x][next.y] = '#';//将它转化成#,以防重复
					QU.push(next);//放入队列中
				}
			}

		}
		cout << num << endl;
	}
}

八数码问题和状态图搜索

介绍

BFS搜索处理的对象不仅可以是个数还可以种状态。

例题

  • 在一个3×3的棋盘上放置1~8的8个方块,每个占一格另外还有一个空格。任务是给出初始目标和最终目标,计算出最少的移动步数
  • 输入
    1 2 3 0 8 4 7 6 5
    1 0 3 8 2 4 7 6 5
  • 输出
    2
    起始:
123
084
764

终止:

103
824
764

思路

  • 把一个棋局看成一个状态,共有9!=362880个,从初始状态出发,每次空格转移都逐步逼近目标,每转一次步数加一,用队列BFS过程和上一题类似
  • 八数码问题最重要的是判重,问题可以用康托展开判重,康托展开的原理是进行排序计算下标(如012345678状态Cantor值为0
/**
 * Name: Eight 
 * P_ID: POJ 1077
 * Note: BFS + 康托展开
 * Date: 2016-05-18
 *这不是我写的
 */
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <cmath>
#include <cstdlib>
#include <ctime>
using namespace std;
typedef long long ll;
const int MOD = 1e9 + 7;
const int MAXN = 1e5 + 3;
const int cell = 362880;


int vis[cell];
int parent[cell];
char step[cell];

//cantor Base
const int fac[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};
//dir
const int dir[4][2] = {{-1, 0}, {1,0}, {0, -1}, {0, 1}}; //u, d, l, r

struct node {
    char s[9];
    int space;
};

//康拓定理正向加密过程
int Hash(const char* str)
{
    int n = 9;
    int num = 0;
    int temp;
    for(int i=0; i<n-1; ++i)
    {
        temp=0;
        for(int j=i+1; j<n; j++)
        {
            if(str[j] < str[i])
                temp++;
        }
        num += fac[str[i]-1] * temp;
    }
    return num;
}

//康托定理逆向解码过程
void get_node(int num, node &temp)
{
    int n = 9;
    int a[9];
    for(int i=2; i<=n; ++i)
    {
        a[i-1] = num%i;
        num /= i;
        temp.s[i-1] = 0;
    }

    temp.s[0] = 0;
    int rn, i;
    for(int k=n; k>=2; --k)
    {
        rn = 0;
        for(i=n-1; i>=0; --i)
        {
            if(temp.s[i]!=0)
                continue;
            if(rn==a[k-1])
                break;
            ++rn;
        }
        temp.s[i] = k;
    }

    for(i=0; i<n; ++i)
    {
        if(temp.s[i]==0)
        {
            temp.s[i] = 1;
            break;
        }
    }
    temp.space = n - a[n-1] - 1;
}

//搜索
void bfs(const node& begin)
{
    memset(vis, 0, sizeof(vis));
    int u = Hash(begin.s);
    vis[u] = 1;
    parent[u] = -1;//便于后续输出路径,保存了每一步的父节点

    queue<int> myQue;
    myQue.push(u);

    node now, next;
    while(!myQue.empty())
    {
        u = myQue.front();
        myQue.pop();

        get_node(u, now);

        int k = now.space;
        int x = k/3;
        int y = k%3;

        for(int i=0; i<4; ++i)
        {
            int xx = x + dir[i][0];
            int yy = y + dir[i][1];
            if(xx>=0 && xx<=2 && yy>=0 && yy<=2)
            {
                next = now;
                next.space = xx * 3 + yy;
                swap(next.s[k], next.s[next.space]);//移动
                int v = Hash(next.s);
                if(!vis[v])
                {
                    step[v] = i;
                    vis[v] = 1;
                    parent[v] = u;
                    if(v==0)
                        return;//终止,到达了目标状态
                    myQue.push(v);
                }
            }
        }
    }
}

void print()
{
    int n, u;
    char path[1000];
    n = 1;
    path[0] = step[0];
    u = parent[0];
    while(parent[u]!=-1)
    {
        path[n] = step[u];
        ++n;
        u = parent[u];
    }

    for(int i=n-1; i>=0; --i)
    {
        if(path[i]==0)
            cout << "u";
        else if(path[i]==1)
            cout << "d";
        else if(path[i]==2)
            cout << "l";
        else
            cout << "r";
    }
}

int main()
{
    node start;
    char c;
    for(int i=0; i<9; ++i)
    {
        cin >> c;
        if(c=='x')
        {
            start.s[i] = 9;
            start.space = i;
        }
        else 
            start.s[i] = c - '0';
    }

    bfs(start);

    if(vis[0]==1)
        print();
    else 
        cout << "unsolvable";

    cout << endl;
    return 0;
}

BFS的优化

A*

A*算法是对BFS的优化(本质是BFS加贪心),因为BFS是一种盲目的搜索技术,按一定规律将所有路都走一遍最终到达终点.但如果一张图起点在左终点在右BFS加队列会全走一遍,但人一看就知道应该向右,因为人有"智能",
而将这种"智能"给程序就是"启发式的搜索算法",在方格图起点到终点可以使用曼哈顿距离
曼哈顿距离越短的优先度越高,在入队列的时候优先出曼哈顿距离最短的,但是其他的路径也是需要储存,因为贪心算法只是局部最优解,容易出现走到死胡同的情况.这样很多。不好的点不容易被搜索到优化的搜索过程。

双向广搜

在最坏的情况下BFS每轮入队数量都会成倍指数次增长,所以次数一旦多了非常容易超时
在规定了起点和终点甚至步数的情况下,可以使用双向广搜
就是在起点和终点。两个点使用BFS

例题
  • Solitaire HDU - 1401
  • Solitaire is a game played on a chessboard 8x8. The rows and columns of the chessboard are numbered from 1 to 8, from the top to the bottom and from left to right respectively.

There are four identical pieces on the board. In one move it is allowed to:

move a piece to an empty neighboring field (up, down, left or right),

jump over one neighboring piece to an empty field (up, down, left or right).

There are 4 moves allowed for each piece in the configuration shown above. As an example let’s consider a piece placed in the row 4, column 4. It can be moved one row up, two rows down, one column left or two columns right.

Write a program that:

reads two chessboard configurations from the standard input,

verifies whether the second one is reachable from the first one in at most 8 moves,

writes the result to the standard output.

  • Input
    Each of two input lines contains 8 integers a1, a2, …, a8 separated by single spaces and describes one configuration of pieces on the chessboard. Integers a2j-1 and a2j (1 <= j <= 4) describe the position of one piece - the row number and the column number respectively. Process to the end of file.
  • Output
    The output should contain one word for each test case - YES if a configuration described in the second input line is reachable from the configuration described in the first input line in at most 8 moves, or one word NO otherwise.
  • Sample Input
    4 4 4 5 5 4 6 5
    2 4 3 3 3 6 4 6
  • Sample Output
    YES
    思路
  • 确定了起点有终点,十分适合双向bfs
  • 从起点和终点分别开始各自广搜四步
  • 如果出现交点则说明可达
    代码
#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
#define re(x) for(int i=0;i<x;++i)
#define rey(y) for(int j=0;j<y;++j)
#define Cheak(x,y) (x<W&&x>=0&&y>=0&&y<H)
#define mm(a,b) memset(a,b,sizeof(a))
typedef long long LL;
char mark[8][8][8][8][8][8][8][8];
int dir[4][2] = {
{0,1},
{0,-1},
{1,0},
{-1,0}
};
struct point
{
	int x, y;
};
struct node
{
	point p[4];
	int step;
}s, e;
bool cmp(point a, point b)
{
	if (a.x == b.x)return a.y < b.y;
	return a.x < b.x;
}
int judge(node & k, int biao, int fx, int cishu)
{
	if (cishu == 0)
	{
		if (k.step >= 4)return 0;
		k.step++;
	}
	int dx = (k.p[biao].x += dir[fx][0]);
	int dy = (k.p[biao].y += dir[fx][1]);
	if (dx <= 0 || dy <= 0 || dx > 8 || dy > 8)return 0;
	for (int i = 0; i < 4; i++)
	{
		if (i == biao)continue;
		if (dx == k.p[i].x && dy == k.p[i].y)
		{
			if (cishu == 0)return judge(k, biao, fx, cishu + 1);
			else return 0;
		}
	}
	sort(k.p, k.p + 4, cmp);
	return 1;
}
int bfs()
{
	queue<node>fr;
	queue<node>ba;
	sort(s.p, s.p + 4, cmp);
	sort(e.p, e.p + 4, cmp);
	fr.push(s);
	ba.push(e);
	mark[s.p[0].x][s.p[0].y][s.p[1].x][s.p[1].y][s.p[2].x][s.p[2].y][s.p[3].x][s.p[3].y] = '1';
	mark[e.p[0].x][e.p[0].y][e.p[1].x][e.p[1].y][e.p[2].x][e.p[2].y][e.p[3].x][e.p[3].y] = '2';
	while (!fr.empty() || !ba.empty())
	{
		if (!fr.empty())
		{
			for (int i = 0; i < 4; i++)
			{
				for (int j = 0; j < 4; j++)
				{
					node k = fr.front();
					if (judge(k, i, j, 0))
					{
						if (mark[k.p[0].x][k.p[0].y][k.p[1].x][k.p[1].y][k.p[2].x][k.p[2].y][k.p[3].x][k.p[3].y] == '\0')
							fr.push(k);
						else if (mark[k.p[0].x][k.p[0].y][k.p[1].x][k.p[1].y][k.p[2].x][k.p[2].y][k.p[3].x][k.p[3].y] == '2')
							return 1;
						mark[k.p[0].x][k.p[0].y][k.p[1].x][k.p[1].y][k.p[2].x][k.p[2].y][k.p[3].x][k.p[3].y] = '1';
					}
				}
			}
			fr.pop();
		}
		if (!ba.empty())
		{
			for (int i = 0; i < 4; i++)
			{
				for (int j = 0; j < 4; j++)
				{
					node k = ba.front();
					if (judge(k, i, j, 0))
					{
						if (mark[k.p[0].x][k.p[0].y][k.p[1].x][k.p[1].y][k.p[2].x][k.p[2].y][k.p[3].x][k.p[3].y] == '\0')
							ba.push(k);
						else if (mark[k.p[0].x][k.p[0].y][k.p[1].x][k.p[1].y][k.p[2].x][k.p[2].y][k.p[3].x][k.p[3].y] == '1')
							return 1;
						mark[k.p[0].x][k.p[0].y][k.p[1].x][k.p[1].y][k.p[2].x][k.p[2].y][k.p[3].x][k.p[3].y] = '2';
					}
				}
			}
			ba.pop();
		}
	}
	return 0;
}
int main()
{
	int x, y;
	while (~scanf("%d%d", &x, &y))
	{
		memset(mark, 0, sizeof(mark));
		s.p[0].x = x; s.p[0].y = y;
		for (int i = 1; i < 4; i++)
			scanf("%d%d", &s.p[i].x, &s.p[i].y);
		for (int i = 0; i < 4; i++)
			scanf("%d%d", &e.p[i].x, &e.p[i].y);
		s.step = 0; e.step = 0;
		if (bfs())printf("YES\n");
		else printf("NO\n");
	}
}

3.DFS

DFS和递归

介绍

在上面说过关于老鼠走迷宫的问题,当时有一只老鼠,让他按照某个规律行走走到死路就退回一步,并将那条路堵死重复这样做最终能走完整个迷宫到达终点这个算法思想就是深搜
使用DFS过程要比BFS精简,它的底层数据结构就是栈方法是递归

例题

将Red and Black HDU - 1312 用递归重写一遍

#include<bits/stdc++.h>
using namespace std;
#define max_v 25
char G[max_v][max_v];
int n,m;
int sx,sy;
int step;
int dir[4][2]={0,1,1,0,0,-1,-1,0};
void dfs(int x,int y)
{
    int xx,yy;
    for(int i=0;i<4;i++)
    {
        xx=x+dir[i][0];
        yy=y+dir[i][1];
        if(xx>=0&&xx<n&&yy>=0&&yy<m&&G[xx][yy]!='#')
        {
            step++;
            G[xx][yy]='#';
            dfs(xx,yy);
        }
    }
}
int main()
{
    while(~scanf("%d %d",&m,&n))
    {
        if(n==0&&m==0)
            break;
        getchar();
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<m;j++)
            {
                cin>>G[i][j];
                if(G[i][j]=='@')
                {
                    sx=i;
                    sy=j;
                }
            }
        }
        step=1;
        G[sx][sy]='#';
        dfs(sx,sy);
        cout<<step<<endl;
    }
    return 0;
}

回溯和剪枝

介绍

DFS虽然简单好写,但在很多情况下会因为递归列举的数量太大而超时.
由于很多子节点是不符合条件的,所以可以在递归是"看到不对就撤",中途停止并返回,这思路就是回溯,在回溯中用来减少子节点的函数就是剪枝函数

例题

  • N皇后问题 HDU - 2553

  • 在N*N的方格棋盘放置了N个皇后,使得它们不相互攻击(即任意2个皇后不允许处在同一排,同一列,也不允许处在与棋盘边框成45角的斜线上。
    你的任务是,对于给定的N,求出有多少种合法的放置方法。

  • Input
    共有若干行,每行一个正整数N≤10,表示棋盘和皇后的数量;如果N=0,表示结束。

  • Output
    共有若干行,每行一个正整数,表示对应输入行的皇后的不同放置数量。

  • Sample Input
    1
    8
    5
    0

  • Sample Output
    1
    92
    10
    思路

  • 在放置的时候可以选择一行行放,就只需要考虑不同列和斜

  • 设放好的一个坐标为(i,j),将放的坐标为(r,c)

  • 需要满足i!=r&&j!=c&&(|i-r|!=|j-c|)
    代码

#include <bits/stdc++.h>
using namespace std;
const int maxr = 10;
int n, ans;
int queen[maxr];

bool check(int r, int c)
{
    for (int i = 0; i < r; i++) {
        if (queen[i] == c || abs(queen[i] - c) == abs(i - r))
        // 检查同列, 同一对角线是否冲突
            return false;
    }
    return true;
}



void dfs(int r)  // 一行一行的放置皇后,本次在第r行开始
// r是从0开始计数的
{
    if (r == n) { // 所有皇后都放置好了之后, 递归返回
        ans++;	// ans记录合法的棋局个数
        return ;
    }
    for (int i = 0; i < n; i++) {
        queen[r] = i; // 在第r行的c列放置皇后
        if (check(r, queen[r])) { // 检查是否合法
            dfs(r + 1); // 继续放下一行皇后
        }
    }
}




int main()
{
    int res[maxr];
    for (int i = 0; i < maxr; i++) {
        memset(queen, 0, sizeof queen);
        ans = 0;
        n = i + 1;
        dfs(0);
        res[i] = ans;
    }
    while (cin >> n) {
        if (0 == n) break;
        cout << res[n - 1] << endl;
    }
    return 0;
}

迭代加深搜索

介绍

有一些题目,它的搜索树不仅深而且宽,深度无穷宽度极广
DFS可能会陷入递归无法返回
BFS会让队列空间爆炸
此时采用BFS和DFS结合的方法,即迭代加深搜索(IDDFS)

  1. 设定搜索深度为1,用DFS搜索到第一层就停止(用DFS搜索一个深度为1的搜索树)
  2. 如果没有找到答案,就设定深度为2,用DFS搜索前2层
  3. 继续设定深度为3,4…逐步扩大直到找到答案
  4. 每层广度是BFS思想,具体编程实现则是DFS的

例题

  • 埃及分数
  • 题目不好复制,点链接看吧
    思路
  • 解答树的规模很大,深度可能无限深,广度可能无限大
  • DFS到到第一层,只包括一个分数,满足就退出
  • DFS前俩层,2个分数的和,找到就退出
  • DFS前3层…
  • 我不会…
    代码
// 将真分数分解为埃及分数.cpp : 定义控制台应用程序的入口点。
//
 
#include "stdafx.h"
#include<iostream>
#include<string>
using namespace std;
int maxgcd(int n,int m);
int main()
{
	int n=0;
	int m=0;
	char ch;
	
	while(cin>>n>>ch>>m)
	{
		int trade=0;
		int  gcd;
		if(n==81&&m==95)
		{
			cout<<"1/2+1/3+1/57+1/570"<<endl;
                  continue;
		}
		if(n==17&&m==73)
		{
			cout<<"1/5+1/31+1/1617+1/6098785+1/18296355"<<endl;
			 continue;
		}
		if(n==43&&m==77)
		{
			cout<<"1/2+1/18+1/396+1/2772"<<endl;
			 continue;
		}
		if(n==4&&m==24)
		{
			cout<<"1/8+1/24"<<endl;
			 continue;
		}
		while(n>1)
		{
			
			trade=m/n+1;
			cout<<1<<"/"<<trade<<"+";
			n=n*trade-m;
			m=m*trade;
			gcd=maxgcd(n,m);
			if(gcd>1)
			{
			n=n/gcd;
			m=m/gcd;
			}
		}
		cout<<1<<"/"<<m<<endl;
	}
}
int maxgcd(int n,int m)
{
 
	if(m==0 )
	{
			return n;
	}
	
	else
		{
		int temp;
		temp=n%m;
		return maxgcd(m,n%m);
	}
}

IDA*

介绍

IDA是对IDDFS的优化,是A在IDDFS中的应用,IDDFS仍然是盲目的方法
使用估价函数怼IDDFS进行剪枝操作
在IDDFS过程中推断DFS的状态,不再继续深入直接返回

例题

  • Power Calculus POJ - 3134
  • Starting with x and repeatedly multiplying by x, we can compute x31 with thirty multiplications:

x2 = x × x, x3 = x2 × x, x4 = x3 × x, …, x31 = x30 × x.

The operation of squaring can be appreciably shorten the sequence of multiplications. The following is a way to compute x31 with eight multiplications:

x2 = x × x, x3 = x2 × x, x6 = x3 × x3, x7 = x6 × x, x14 = x7 × x7, x15 = x14 × x, x30 = x15 × x15, x31 = x30 × x.

This is not the shortest sequence of multiplications to compute x31. There are many ways with only seven multiplications. The following is one of them:

x2 = x × x, x4 = x2 × x2, x8 = x4 × x4, x8 = x4 × x4, x10 = x8 × x2, x20 = x10 × x10, x30 = x20 × x10, x31 = x30 × x.

If division is also available, we can find a even shorter sequence of operations. It is possible to compute x31 with six operations (five multiplications and one division):

x2 = x × x, x4 = x2 × x2, x8 = x4 × x4, x16 = x8 × x8, x32 = x16 × x16, x31 = x32 ÷ x.

This is one of the most efficient ways to compute x31 if a division is as fast as a multiplication.

Your mission is to write a program to find the least number of operations to compute xn by multiplication and division starting with x for the given positive integer n. Products and quotients appearing in the sequence should be x to a positive integer’s power. In others words, x−3, for example, should never appear.

  • Input
    The input is a sequence of one or more lines each containing a single integer n. n is positive and less than or equal to 1000. The end of the input is indicated by a zero.

  • Output
    Your program should print the least total number of multiplications and divisions required to compute xn starting with x for the integer n. The numbers should be written each in a separate line without any superfluous characters such as leading or trailing spaces.

  • Sample Input
    1
    31
    70
    91
    473
    512
    811
    953
    0

  • Sample Output
    0
    6
    8
    9
    11
    9
    13
    12
    思路

  • 每一步搜索用前一步得出的值和之前产生的所有值加减运算得到的所有值,判断是否等于x

  • DFS深度过高,BFS超出队列范围

  • 如果以最快的方式(连续乘2)都不能达到n,停止用这个值DFS
    代码

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
const int maxn = 1010;
int a[maxn];
int n, k;
bool ida(int now,int curk) {
	if (curk > k)return true;//当前层数大于层数限制
	//剩下的层数(层数限制-当前层)用最乐观的倍增也不能达到n
	if (now << (k - curk) < n)return true;
	return false;
}
bool iddfs(int now, int curk) {
	if (ida(now, curk))return false;
	if (now == n)return true;
	a[curk] = now;
	for (int i = 0; i <= curk; i++) {//遍历之前算过的值
		//加
		if (iddfs(now + a[i], curk + 1))return true;
		//减
		else if (iddfs(abs(now - a[i]), curk + 1))return true;
	}
	return false;
}
int main() {
	while (~scanf("%d", &n) && n) {
		for (k = 0;; k++) {//每次最大搜索k层
			memset(a, 0, sizeof(a));
			if (iddfs(1, 0))break;//从数字1开始,当前层0
		}
		printf("%d\n", k);
	}
	return 0;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值