ACM第五次寒假集训

ACM第五次寒假集训

自然数的拆分问题

洛谷 - P2404

思路

如果一个函数的作用是将一个数字拆成一些数字相加的形式,那么我们可以考虑用这个函数来实现数值更大的数字的拆分

i+dfs(sum-i);就可以表示 数值 i + 和为(sum-i)的一些排列方式,为了防止会有重复,同时有字典序,所以我们设置函数保证这个函数拆分出来的和数最小值为 i ,然后依次从i 到 sum-i遍历,类似于实现全排列的方式,这样也能保证所有的和数是非降得,递归得到所有可能的结果。

代码

#include<iostream>
#include<vector>
using namespace std;
vector<int> ans;
void and_num(int sum, int min_and) {
	if (sum == 0) {
		if(ans.size()==1) return;
		for (int i = 0; i < ans.size(); ++i) {
			cout << ans[i];
			if (i != ans.size() - 1) cout << '+';
		}cout << endl;
		return;
	}
	for (int i = min_and; i <= sum ; ++i) {
		if (sum - i >= i||sum-i==0) {
			ans.push_back(i);
			and_num(sum - i, i);
			ans.pop_back();
		}
	}
}
void solve() {
	int n;
	cin >> n;
	and_num(n, 1);
}
int main() {
	ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
	int T = 1;
	//cin >> T;
	while (T--) solve();
	return 0;
}

填涂颜色

洛谷 - P1162

思路

本题主要是使用广度优先搜索,我是先从第一开始逐个判断是不是圈内得 0 ,如果能遍历到边界外,说明不是,判断下一个,如果是圈内得 0 ,直接广度搜索一下就能把圈内得 0 全部遍历一遍,停止遍历即可得到答案

代码

#include<iostream>
#include<queue>
using namespace std;
const int MAX = 31;
int maze[MAX][MAX],n;
bool vis[MAX][MAX];
struct point {
	int x, y;
	point(int x=0,int y=0):x(x),y(y){}
	point operator+(point& b) {
		return point(x + b.x, y + b.y);
	}
}dirt[4] = { point(0,1),point(0,-1),point(1,0),point(-1,0) };//方向:下,上,右,左
bool bfs(point start) {//广度优先遍历
	for (int i = 1; i <= n; ++i)
		for (int j = 1; j <= n; ++j)
			vis[i][j] = false;
	queue<point> q;
	q.push(start);
	vis[start.x][start.y] = true;
	while (!q.empty()) {
		point now = q.front(); q.pop();
		point next;
		for (int i = 0; i < 4; ++i) {
			next = now + dirt[i];
			if (next.x < 1 || next.x > n || next.y < 1 || next.y > n) return false;
			else if (vis[next.x][next.y]==false&&maze[next.x][next.y]!=1) {
				vis[next.x][next.y] = true;
				q.push(next);
			}
		}
	}
	return true;
}
void solve() {
	cin >> n;
	for (int i = 1; i <= n; ++i)
		for (int j = 1; j <= n; ++j)
			cin >> maze[i][j];
	point point_in;
	bool flag = false;
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= n; ++j) {
			if (maze[i][j] == 1) continue;
			point_in = point(i, j);
			flag = bfs(point_in);
			if (flag) break;
		}
		if (flag) break;
	}
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= n; ++j) {
			if (vis[i][j] == true) maze[i][j] = 2;
			cout << maze[i][j];
			if (j != n) cout << " ";
		}
		if (i != n) cout << endl;
	}
}
int main() {
	ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
	int T = 1;
	//cin >> T;
	while (T--) solve();
	return 0;
}

显示图像

洛谷 - P1256

思路

曼哈顿长度 len= |x2-x1| + |y2-y1| ;这样以(x1,y1)为中心得点相邻得位置为上下左右四个方向。

互通得白色区块周围相邻得区块得最短长度就是 1 ,然后再以这些到白色区块最短距离为一得区块为中心,向外延申,得到得周围相邻得区块到白色区块得最近距离再 +1 即可。依次类推,用广度优先搜索即可达成

代码

#include<iostream>
#include<queue>
using namespace std;
const int MAX = 182 + 2;
int n, m,maze[MAX][MAX],vis[MAX][MAX],ans[MAX][MAX];
int dx[] = { 0,1,0,-1 };
int dy[] = { 1,0,-1,0 };
int main() {
	queue<pair<int, int>> q;
	cin >> n >> m;
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= m; ++j) {
			char ch;
			cin >> ch;
			maze[i][j] = ch ^ 48;
			if (maze[i][j] == 1) q.push({ i,j }),vis[i][j]=1;
		}
	}
	while (!q.empty()) {
		pair<int, int> now = q.front(); q.pop();
		pair<int, int> next;
		for (int i = 0; i < 4; ++i) {
			int tx = now.first + dx[i];
			int ty = now.second + dy[i];
			if (tx<1 || tx>n || ty<1 || ty>m) continue;
			if (!vis[tx][ty]&&maze[tx][ty]==0) {
				vis[tx][ty] = 1;
				ans[tx][ty] = ans[now.first][now.second]+1;
				q.push({ tx,ty });
			}
		}
	}
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= m; ++j) {
			cout << ans[i][j];
			if (j != m) cout << " ";
		}
		if (i != n) cout << endl;
	}
	return 0;
}

健康的荷斯坦奶牛 Healthy Holsteins

洛谷 - P1460

思路

我们可以采用遍历所有可能得方式来得到最终得答案。

每种饲料有两种可能,要么这种饲料存在,要么这种饲料不存在,所有有G种饲料就有 2^G可能,当所有饲料都有选择得加入了一次了,此时判断当前方案是否能够满足需求,即当前方案每一种维生素得总量是否能符合要求,如果不符合要求,直接舍弃这种方案,如果发现这种方案可以,就与当前得最佳方案比较,如果比最佳方案还佳,那就把当前得最佳方案替换掉,如果没有当前得最佳方案好,那就舍弃这种方案进行下一种方案的检测。

代码

#include<iostream>
#include<climits>
using namespace std;
int v, g,kind[30],food[20][30],ans[20],min_steps=INT_MAX,min_ans[20];//ans数组是每一次的结果数组,min_steps数组是存储的需要步数最小的答案啊数组
bool check(int steps) {
	for (int j = 1; j <= v; ++j) {
		int sum = 0;
		for (int i = 1; i <= steps; ++i) {
			sum += food[ans[i]][j];
		}
		if (sum < kind[j]) return false;
	}
	return true;
}
void dfs(int ki,int st) {
	if (ki > g) {
		if (check(st)) {
			if (st < min_steps) {
				min_steps = st;
				for (int i = 1; i <= min_steps; ++i) min_ans[i] = ans[i];
			}
		}
		return;
	}
	ans[st + 1] = ki;
	dfs(ki + 1, st + 1);
	ans[st + 1] = 0;
	dfs(ki + 1, st);
}

int main() {
	cin >> v;
	for (int i = 1; i <= v; ++i) cin >> kind[i];
	cin >> g;
	for (int i = 1; i <= g; ++i)
		for (int j = 1; j <= v; ++j) cin >> food[i][j];
	dfs(1, 0);
	cout << min_steps;
	for (int i = 1; i <= min_steps; ++i) {
		cout << " " << min_ans[i];
	}
	return 0;
}

GRZ-Ridges and Valleys

洛谷 - P3456

思路

根据题意发现某个结点相邻的结点为相邻的八个方位。

从第一个开始遍历,将这一个结点打上已被访问的标记,广度优先遍历这个结点的一周,如果发现有相同高度的就加进队列中,然后再继续遍历,但是由于前面那个等势的点已经被打上了标记,所以不会再次被放进队列里面。这样将等势的互通的地方打上标记,循环可以结束,并运用广度优先搜索遍历一遍这些互通的等势点的周围,判断是否有比自己高的,如果有比自己高的,说明这些不是山峰,如果这一周有比自己低的说明这些也不是山谷,应注意也可能是山峰还是山谷。因为被访问的互通的等势块都被打上了标记,所以也保证这些不会等势块后面不会再次被bfs 到,不仅提高了遍历的效率,还保证了结果的准确。

代码

#include<iostream>
#include<queue>
using namespace std;
struct point {
	int x, y;
	point(int xx=0,int yy=0):x(xx),y(yy){}
	point operator+(point& b) {
		return point(x + b.x, y + b.y);
	}
}dirt[8] = {point(0,1),point(1,1),point(1,0),point(1,-1),point(0,-1),point(-1,-1),point(-1,0),point(-1,1)};
const int MAX = 1001;
int n,mount[MAX][MAX],vis[MAX][MAX], cnt_h = 0, cnt_l = 0;
bool hi = true, lo = true;
void bfs(point start) {
	hi = true, lo = true;
	queue<point> isossce;//等势
	vis[start.x][start.y] = -1;
	isossce.push(start);
	while (!isossce.empty()) {//不能提前退出,否则就会有相邻等势的地方遍历不到
		point now = isossce.front(); isossce.pop();
		point next;
		for (int i = 0; i < 8; ++i) {
			next = now + dirt[i];
			if (next.x<1 || next.x>n || next.y<1 || next.y>n) continue;
			if (vis[next.x][next.y] != -1&&mount[next.x][next.y] == mount[now.x][now.y]) {
				vis[next.x][next.y] = -1;
				isossce.push(next);
			}
			else if (hi && mount[next.x][next.y] > mount[now.x][now.y]) hi = false;
			else if (lo && mount[next.x][next.y] < mount[now.x][now.y]) lo = false;
		}
	}
	if (hi && lo) ++cnt_h, ++cnt_l;
	else if (hi && !lo) ++cnt_h;
	else if (!hi && lo) ++cnt_l;
}
int main() {
	cin >> n;
	for (int i = 1; i <= n; ++i)
		for (int j = 1; j <= n; ++j) cin >> mount[i][j];
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= n; ++j) {
			if (vis[i][j] != -1) bfs(point(i, j));
		}
	}
	cout << cnt_h << " " << cnt_l;
	return 0;
}

八皇后 Checker Challenge

洛谷 - P1219

思路

这道题很像给一个数字,然后写出这些数字的全排列。本题对于每一行棋子的位置都有n种选择,一共有n行,那么总可能数就是n的阶乘,这样想和求数字n的全排列的总可能是类似的,但是不同的是这里还要求与两条对角线平行的线上也不能有两个及以上的棋子,有且仅有1个棋子,所以可以利用这一点进行剪枝。每放进一个新的点,就判断这个点是否与之前存进答案vector里已经有的点的连线的斜率的绝对值是否为 1 如果为 1 ,那么这种方案是不可行的,就退回到没有放进这个新点的状态,在这一行往下遍历,选择一个新的点进行判断,如果可以就放进去进行下一层的判断,当本层的所有可能都被判断过一遍后退出当前状态,返回到上一层状态。当最终检测到发现答案vector中存有的答案值有n个,说明得到了一组解,将这组解输出即可,但是注意题目要求输出三组解,并且按照字典序。我们可以对已经产生的解决方案的个数进行计数,当计数到三了后就不再输出解,只进行计数。至于字典序,我们全排列的每层从小到达遍历然后进入下一层,这样得到的结果本身就是字典序的,如果其他方法需要将所有答案存起来,然后按字典序对所有解排序然后输出前三个。然后还要输出总共解的个数。

代码

#include<iostream>
#include<vector>
#include<cmath>
using namespace std;
const int MAX = 15;
struct point {
	int x, y;
	point(int xx=0,int yy=0):x(xx),y(yy){}
};
vector<point> ans;
double get_tan(const point&a,const point&b) {
	return abs(b.x - a.x) * 1.0 / abs(b.y - a.y);
}
bool check(int cnt,const point&new_point) {
	for (int i = 0; i < cnt; ++i) {
		if (get_tan(ans[i], new_point) == 1) return false;
	}
	return true;
}
int N,cnt=0,vis[MAX],cnt_ans=0;
void solve(int& cnt) {
	if (cnt == N) {
		if (++cnt_ans <= 3) {
			for (int i = 0; i < cnt; ++i) {
				cout << ans[i].y;
				if (i != cnt - 1) cout << " ";
			}
			cout << endl;
		}
		return;
	}
	for (int i = 1; i <= N; ++i) {
		if (!vis[i]&& check(cnt, point(cnt + 1, i))) {
			vis[i] = 1;
			ans.push_back(point(++cnt, i));
			solve(cnt);
			vis[i] = 0;
			--cnt;
			ans.pop_back();
		}
	}
}
int main() {
	cin >> N;
	solve(cnt);
	cout << cnt_ans;
	return 0;
}

学习总结

全排列(算是一种深度搜索)

const int MAX=20;
int N,cnt=0;
bool vis[MAX];
vector<int> ans;
void all_order(int&cnt){
	if(cnt==N){
        for(int i=0;i<N;++i){
			cout<<ans[i];
            if(i!=N-1) cout<<" ";
        }
        cout<<endl;
        return ;
    }
    for(int i=1;i<=N;++i){
		if(!vis[i]){
            vis[i]=1;
            ans.push_back(i);
            all_order(++cnt);
            vis[i]=0;
            --cnt;
            ans.pop_back();
        }
    }
}

深度优先搜索

//以四方向相邻为例
#include<bits/stdc++.h>
using namespace std;
int dx[]={0,0,1,-1};
int dy[]={1,-1,0,0};
const int maxn=2;
int maze[maxn+1][maxn+1],visited[maxn][maxn];//maze数组中0表示通路,1表示有障碍物,不能达到。
void dfs(pair<int,int>start) {
    for (int i=0;i<4;i++) {
        int tx=start.first+dx[i];
        int ty=start.second+dy[i];
        if (visited[tx][ty]==0&&maze[tx][ty]==0) {
            visited[tx][ty]=1;
            //对(tx,ty)这个点进行操作
            dfs({tx,ty});
            visited[tx][ty]=0;//恢复访问状态
            //恢复到没操作之前
        }
    }
}


int main() {
    for (int i=1;i<=maxn;++i)
        for (int j=1;j<=maxn;++j) cin>>maze[i][j];
    dfs({1,1});//这样能保证所有通路都被访问到
    return 0;
}

广度优先搜索

#include<bits/stdc++.h>
using namespace std;
int dx[]={0,0,1,-1};
int dy[]={1,-1,0,0};
const int maxn=2;
int maze[maxn+1][maxn+1],visited[maxn][maxn];//maze数组中0表示通路,1表示有障碍物,不能达到。
void bfs(pair<int,int>start) {
    queue<pair<int,int>> q;
    q.push(start);
    while(!q.empty()) {
        pair<int,int> now = q.front();q.pop();
        for (int i=0;i<4;++i) {
            int tx=now.first+dx[i];
            int ty=now.second+dy[i];
            if (tx<1||tx>maxn||ty<1||ty>maxn) continue;//出界了
            if (visited[tx][ty]==0&&maze[tx][ty]==0) {
                visited[tx][ty]=1;
                q.push({tx,ty});
            }
        }

    }
}

int main() {
    for (int i=1;i<=maxn;++i)
        for (int j=1;j<=maxn;++j) cin>>maze[i][j];
    bfs({1,1});//这样能保证所有通路都被访问到
    return 0;
}

posted @ 2025-02-13 21:25  Buy-iPhone  阅读(9)  评论(0)    收藏  举报