算法入门刷题笔记 2020北京理工大学ACM冬训课堂笔记:第九讲 - DFS&BFS&图 && A - Fixing Banners - J - Cyclic Component

写在前面

好久没更新公众号和博客了,因为最近在研究新的方向,所以很少发文。
笔者接触编程只有一年,这一年间主要研究启发式算法在运筹学中的应用。但是由于编程基础薄弱,在进一步研究复杂运筹学问题时发现基础算法不过关导致写出的代码运行速度很慢,因此很苦恼。所以决定这个暑假补习一下基础算法,主要是刷一些简单的ACM入门题。偶尔会发一些刷题笔记(偶尔!)。和作者有类似目标的同学可以一起交流共勉!

目前在看的教程:
北京理工大学ACM冬季培训课程

算法竞赛入门经典/刘汝佳编著.-2版可以在这里下载->github

课程刷题点
Virtual Judge

刷题代码都会放在github上,欢迎一起学习进步!

昨天不是没刷,是懒得写博客了,今天一并记上。这期笔记可能比较杂,作者是看了紫书第六章后再跳到着讲看的,大家可以先看下我博客里关于紫书第六讲的内容。

P9 - DFS&BFS&图

next_permutation()函数可以生成下一个全排列,并判断是否已经生成n!个全排列。(需要定义<)

在这里插入图片描述

DFS求全排序时间复杂度O(n!)

集合的枚举:选或不选。

在这里插入图片描述

复杂度O(2^n)

位运算处理集合速度非常快

数独与剪枝:

V1.0:所有位置填满1~9的数字,类似全排列。

V2.0:可行性剪枝:每填入一个数字check一次。所有空位置上填合法数字,填满即为答案。

V3.0:搜索顺序剪枝:在所有位置中,总是选择合法数字最少的位置来填。(对搜索可行解的题目)

剪枝没有改变复杂度。

枚举一个点周围四个点:利用数组高效实现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5NNGPbLA-1594541432369)(mdPics/image-20200712115539278.png)]

八连块问题,用dfs做比较简单,紫书U6中有过。

迷宫问题:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5G991e9a-1594541432371)(mdPics/image-20200712115930477.png)]

图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xbdGuCdm-1594541432373)(mdPics/image-20200712144840792.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iMi1Nk3B-1594541432374)(mdPics/image-20200712144955015.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uKD4lvaD-1594541432375)(mdPics/image-20200712145217045.png)]

输出的一些操作:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PX6MzAdC-1594541432376)(mdPics/image-20200712151948369.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hmC4HFlB-1594541432377)(mdPics/image-20200712152355490.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nb25NGOx-1594541432378)(mdPics/image-20200712152428019.png)]

pair<int, int>+vector实现邻接表存储带权图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iGztY7xB-1594541432379)(mdPics/image-20200712153045990.png)]

存图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uObvIPYq-1594541432380)(mdPics/image-20200712153341039.png)]

DFS遍历:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ueoFmYfG-1594541432381)(mdPics/image-20200712153531284.png)]

BFS遍历,顺便找到某起点最短路距离(dis数组):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iPkFpB8H-1594541432382)(mdPics/image-20200712154518008.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-THVRr1IH-1594541432383)(mdPics/image-20200712155059266.png)]

刷题点;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3WhyD94F-1594541432384)(mdPics/image-20200712155318097.png)]

bfs和dfs难度不大~

L题和之后难度很大,不用强求。

下面是习题部分。

A - Fixing Banners

在这里插入图片描述
有6串字符串,问能否从每串中截取一个字符,构成harbin。

咋看是遍历字符串的问题,但是每个字符串长度有 2 ∗ 1 0 6 2*10^{6} 2106之多,直接遍历就是 12 ∗ 1 0 6 12*10^{6} 12106,达到了 1 0 7 10^7 107次级,可能会TLE。结合这讲的主题DFS,可能是考虑用DFS做。

每次dfs搜索第k个字符串,找到harbin中某个未搜索到的字母,则进入下一个字符串,若找不到,返回false。

以前不知道,做过这个专题才发现dfs、bfs有好多妙用,适用范围很广。不过bfshedfsnandubuda也是真的(doge)。

#include <iostream>
#include <sstream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <string>
#include <set>
#include <queue>
#include <map>
#include <stack>
#include <unordered_map>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;
#define INT_MAX 0x7fffffff
#define INT_MIN 0x80000000
#define LOCAL

string banners[10];
char ki[6] = {'h', 'a', 'r', 'b', 'i', 'n'};
int vis[6];

bool dfs(int ban)
{
    // 边界判断
    bool flag = true;
    for(auto exist: vis)
        if(!exist)
        {
            flag = false;
            break;
        }
    if(flag) 
        return true;

    for(int i = 0; i < banners[ban].size(); i++)
    {
        for(int j = 0; j < 6; j++)
            if (banners[ban][i] == ki[j])
            {
                vis[j] = 1;
                if(dfs(ban + 1))
                    return true;
                else
                {
                    vis[j] = 0;
                    break;
                }
            }
    }
    return false;
}



int main()
{
#ifdef LOCAL
    freopen("data.in", "r", stdin);
    // freopen("data.out", "w", stdout);
#endif
    int n;
    cin >> n;
    while(n--){
        for(int i = 0; i < 6; i++)
            cin >> banners[i];
        memset(vis, 0, sizeof(vis));
        if(dfs(0))
            cout << "Yes" << endl;
        else
            cout << "No" << endl;
    }
    return 0;
}

B - Rescue

在这里插入图片描述
走迷宫,迷宫中路径的权值可以是1或2。

这题气死我了。昨天半夜做,做出来的答案我搞错删了,还瞎改了好久。气。就拿了一份别人的来,反正写的和我差不多。

走迷宫一般用BFS。因为路径权值不一样,要搞一个优先队列,就能保证第一个到达目的地的路径最短。

#include <iostream>
#include <sstream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <string>
#include <set>
#include <queue>
#include <map>
#include <stack>
#include <unordered_map>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;
#define INT_MAX 0x7fffffff
#define INT_MIN 0x80000000
// #define LOCAL

int n, m;
char graph[205][205];
int sx, sy, ex, ey;
int dir[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
struct node
{
    int x, y, step;
    friend bool operator<(node a, node b)
    {
        return a.step > b.step;
    }
} a, temp;
int bfs()
{
    a.x = sx;
    a.y = sy;
    a.step = 0; //初始化从'r'开始走的位置
    priority_queue<node> q;
    q.push(a); //入栈
    while (!q.empty())
    {
        a = q.top(); //取栈顶赋予a
        q.pop();
        if (a.x == ex && a.y == ey)
            return a.step; //判断是否遇到了'a'
        for (int i = 0; i < 4; i++) //进行探索上下左右
        {
            temp.x = a.x + dir[i][0];
            temp.y = a.y + dir[i][1];
            if (temp.x < n && temp.x >= 0 && temp.y < m && temp.y >= 0 && graph[temp.x][temp.y] != '#') //判断是否能走
            {
                if (graph[temp.x][temp.y] == '.' || graph[temp.x][temp.y] == 'a')
                    temp.step = a.step + 1;
                else
                    temp.step = a.step + 2;
                graph[temp.x][temp.y] = '#';
                q.push(temp);
            }
        }
    }
    return 0;
}
int main()
{
#ifdef LOCAL
    freopen("data.in", "r", stdin);
    // freopen("data.out", "w", stdout);
#endif
    int ans;
    while (scanf("%d%d", &n, &m) != EOF)
    {
        for (int i = 0; i < n; i++)
            scanf("%s", graph[i]);
        for (int i = 0; i < n; i++)
        {
            for (int j = 0; j < m; j++)
            {
                if (graph[i][j] == 'r')
                {
                    sx = i;
                    sy = j;
                }
                if (graph[i][j] == 'a')
                {
                    ex = i;
                    ey = j;
                }
            }
        }
        ans = bfs();
        if (ans)
            printf("%d\n", ans);
        else
            printf("Poor ANGEL has to stay in the prison all his life.\n");
    }
    return 0;
}

C - Prime Ring Problem

在这里插入图片描述
特地查了一下AK是啥意思,不得不说信息竞赛大佬们骚话真的多,相比之下物竞数竞真的太淳朴了。

DFS。order[]数组存放顺序,m == 1 || isPrime(order[m - 1] + order[m])则dfs(m+1)。

#include <iostream>
#include <sstream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <string>
#include <set>
#include <queue>
#include <map>
#include <stack>
#include <unordered_map>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;
#define INT_MAX 0x7fffffff
#define INT_MIN 0x80000000
// #define LOCAL

int n;
int vis[25];
int order[25];

bool isPrime(int n)
{
    for (int i = 2; i <= sqrt(n); i++)
    {
        if ((n % i) == 0)
            return false;
    }
    return true;
}

void dfs(int m)
{
    // for (int i = 1; i <= m - 1; i++)
    //     printf("%d ", order[i]);
    // cout << endl;
    if ((n + 1) == m && isPrime(order[1] + order[n]))
    {
        for (int i = 1; i <= n; i++)
            printf("%d ", order[i]);
        cout << endl;
        return;
    }

    for (int i = 1; i <= n; i++)
    {
        if (!vis[i])
        {
            vis[i] = 1;
            order[m] = i;
            if (m == 1 || isPrime(order[m - 1] + order[m]))
                dfs(m + 1);
            vis[i] = 0;
        }
    }
}

int main()
{
#ifdef LOCAL
    freopen("data.in", "r", stdin);
    // freopen("data.out", "w", stdout);
#endif
    int kase = 0;
    while (cin >> n)
    {
        if(kase)
            cout << endl;
        printf("Case %d:\n", ++kase);
        memset(vis, 0, sizeof(vis));
        memset(order, 0, sizeof(order));
        order[1] = 1;
        vis[1] = 1;
        dfs(2);
    }
    return 0;
}

D - Lake Counting

在这里插入图片描述
前两天紫书刚做过。改一下字符即可AC。DFS。

#include <iostream>
#include <sstream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <string>
#include <set>
#include <queue>
#include <map>
#include <stack>
using namespace std;

typedef long long ll;
#define INT_MAX 0x7fffffff
#define INT_MIN 0x80000000
// #define LOCAL

void swap(int &x, int &y)
{
    int temp = x;
    x = y;
    y = temp;
}

const int maxn = 100 + 5;
char pic[maxn][maxn];
int m, n;
int idx[maxn][maxn]; //idx标记是否访问过

void dfs(int r, int c, int id)
{
    if (r < 0 || r >= m || c < 0 || c >= n)
        return; //"出界"的格子
    if (idx[r][c] > 0 || pic[r][c] != 'W')
        return;     //不是"@"或者已经访问过的格子
    idx[r][c] = id; //连通分量编号 (其实标记为1就够了)
    for (int dr = -1; dr <= 1; dr++)
        for (int dc = -1; dc <= 1; dc++)
            if (dr != 0 || dc != 0)
                dfs(r + dr, c + dc, id);
}

int main()
{
#ifdef LOCAL
    freopen("data.in", "r", stdin);
    // freopen("data.out", "w", stdout);
#endif
    while (scanf("%d%d", &m, &n) == 2 && m && n)
    {
        for (int i = 0; i < m; i++)
            scanf("%s", pic[i]);
        memset(idx, 0, sizeof(idx));
        int cnt = 0;
        for (int i = 0; i < m; i++)
            for (int j = 0; j < n; j++)
                if (idx[i][j] == 0 && pic[i][j] == 'W')
                    dfs(i, j, ++cnt);
        printf("%d\n", cnt);
    }
    return 0;
}

F - Smallest Difference

在这里插入图片描述
第一步全排列,第二步从中间切割。这题应该是考第一张图里的next_permutation函数应用。

#include <iostream>
#include <sstream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <string>
#include <set>
#include <queue>
#include <map>
#include <stack>
// #include <unordered_map>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;
#define INT_MAX 0x7fffffff
#define INT_MIN 0x80000000
// #define LOCAL

void swap(int &x, int &y)
{
    int temp = x;
    x = y;
    y = temp;
}

int num[1005];
int n, len;
int ans;

int main()
{
#ifdef LOCAL
    freopen("data.in", "r", stdin);
    // freopen("data.out", "w", stdout);
#endif
    int n;
    cin >> n;
    cin.get();
    while (n--)
    {
        len = 0;
        string str;
        getline(cin, str);
        stringstream ss(str);
        while(ss >> num[len]) 
            len++;

        if (len == 2) //就两个数
        {
            cout << abs(num[0] - num[1]) << endl;
            continue;
        }

        int n1, n2;
        ans = INT_MAX;
        int mid = len / 2;
        do
        {
            n1 = num[0], n2 = num[mid];
            if (n1 == 0 || n2 == 0)
                continue;
            for (int i = 1; i < mid; i++)
                n1 = n1 * 10 + num[i];
            for (int i = mid + 1; i < len; i++)
                n2 = n2 * 10 + num[i];
            ans = min(ans, abs(n1 - n2));
        } while (next_permutation(num, num + len)); //全排列
        cout << ans << endl;
    }

    return 0;
}

G - Divide by three, multiply by two

在这里插入图片描述
严重怀疑泰泰学长是真人被黑。

专题有注释,这题用来练习一般图里的DFS。所以我还是用的DFS,但是没用到图。现在明白了,大概是要用数字表示点,邻接表表示图, 因为只有两条边。不过后面有一题用的邻接表DFS,算是补回来了。

#include <iostream>
#include <sstream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <string>
#include <set>
#include <queue>
#include <map>
#include <stack>
#include <unordered_map>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;
#define INT_MAX 0x7fffffff
#define INT_MIN 0x80000000
// #define LOCAL

int n;
int vis[25];
int order[105];
ll num[105];

bool dfs(int cur, int m)
{
	if (n == m)
	{
		for (int i = 0; i < n; i++)
			printf("%d ", num[order[i]]);
		cout << endl;
		return true;
	}

	ll x1 = num[cur] * 2, x2 = 0;
	if (num[cur] % 3 == 0)
		x2 = num[cur] / 3;

	for (int i = 0; i < n; i++)
	{
		if (!vis[i])
		{
			if (num[i] == x1 || (x2 && num[i] == x2))
			{
				vis[i] = 1;
				order[m] = i;
				if (dfs(i, m + 1))
					return true;
				vis[i] = 0;
			}
		}
	}
	// cout << "sorry, not find!" << endl;
	return false;
}

int main()
{
#ifdef LOCAL
	freopen("data.in", "r", stdin);
	// freopen("data.out", "w", stdout);
#endif
	cin >> n;
	for (int i = 0; i < n; i++)
		cin >> num[i];
	memset(vis, 0, sizeof(vis));
	memset(order, 0, sizeof(order));
	for (int i = 0; i < n; i++)
	{
		vis[i] = 1;
		order[0] = i;
		dfs(i, 1);
		vis[i] = 0;
	}
	return 0;
}

I - Nearest Opposite Parity

在这里插入图片描述
给一个数组 a [ i ] a[i] a[i],对每一个位于i的点,有一条边连向 i − a [ i ] ( i f 1 ≤ i − a [ i ] ) 或 i + a [ i ] ( i f i + a [ i ] ≤ n ) i−a[i] (if 1≤i−a[i]) 或 i+a[i] (if i+a[i]≤n) ia[i](if1ia[i])i+a[i](ifi+a[i]n)的边。那么用BFS找最短路就ok了。

这里要用邻接表表示图,不然容量很可能超,因为有 2 ∗ 1 0 5 2*10^5 2105个数字。(我不会告诉你们虽然我用了邻接表容量也超了的)

#include <iostream>
#include <sstream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <string>
#include <set>
#include <queue>
#include <map>
#include <stack>
#include <unordered_map>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;
#define INT_MAX 0x7fffffff
#define INT_MIN 0x80000000
// #define LOCAL

int n;
vector<int> arc[200005];
int a[200005];

int bfs(int m)
{
	queue<pii> q;
	q.push({ m, 0 });
	while (!q.empty())
	{
		pii now = q.front();
		q.pop();
		if (now.first != m && (a[now.first] % 2 != a[m] % 2))
			return now.second;
		for (auto to : arc[now.first])
		{
			q.push({ to, now.second + 1 });
		}
	}

	return -1;
}
int main()
{
#ifdef LOCAL
	freopen("data.in", "r", stdin);
	// freopen("data.out", "w", stdout);
#endif
	int ans;
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
		arc[i].clear();
		if ((i - a[i]) > 0 && (i - a[i]) <= n)
			arc[i].push_back(i - a[i]);
		if ((i + a[i]) > 0 && (i + a[i]) <= n)
			arc[i].push_back(i + a[i]);
	}

	for (int i = 1; i <= n; i++)
	{
		ans = bfs(i);
		printf("%d ", ans);
	}

	return 0;
}

J - Cyclic Components

在这里插入图片描述
注意这里的环和connected component连接通量不一样,这里说的是换,且环中除了环内的边外没有其他边。所以每个点的边只有两条。

明显用DFS。一个重要的判断条件是边是否有两条。为了方便终止条件判定,我在dfs传参时加入了pre,最后一个点找不是pre的边(记住只有两条边),判断是否已访问,若访问则为环。这里不能简单用vis数组标记,这样会直接跳过,无法判断终止。

两条边,用邻接表。

#include <iostream>
#include <sstream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <string>
#include <set>
#include <queue>
#include <map>
#include <stack>
#include <unordered_map>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;
#define INT_MAX 0x7fffffff
#define INT_MIN 0x80000000
#define LOCAL

int n, m;
int ct;
vector<int> arc[105];
int vis[100];

bool dfs(int now, int pre)
{
	vis[now] = 1;
	if (arc[now].size() != 2)
		return false;
	int fg = 0;
	for (auto to : arc[now])
	{
		if(to == pre)
			continue;
		if (!vis[to])
		{
			if (dfs(to, now))
				return true;
			else
				return false;
		}
		else if (vis[to] && arc[to].size() == 2)
			return true;
	}
	return false;
}

int main()
{
#ifdef LOCAL
	freopen("data.in", "r", stdin);
	// freopen("data.out", "w", stdout);
#endif
	int ct = 0;
	scanf("%d %d", &n, &m);
	memset(vis, 0, sizeof(vis));
	for (int i = 0; i < m; i++)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		arc[u].push_back(v);
		arc[v].push_back(u);
	}

	ct = 0;
	for (int i = 1; i <= n; i++)
	{
		if (!vis[i])
		{
			if (dfs(i, -1))
				ct++;
		}
	}
	printf("%d\n", ct);

	return 0;
}

刷到这里可能会有同学注意到我跳了H和E,因为看Overview就知道很麻烦…看了下H,确实不太会,AC的答案又都没share,就很难受…所以这个专题我就刷到这里了。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值