POJ-1852
1、不能按照题意走,读完题目后一定要重新建模,否则所有的题最后都会被写成暴力。
2、这种棍子上密密麻麻蚂蚁来回爬动的场景,从直觉上来看不好模拟。有点像两个城市间两个人相向而行,一条狗在中间跑来跑去的小学奥数题,其实是非常简单的模型。
#include <algorithm>
#include <iostream>
using namespace std;
int main() {
ios::sync_with_stdio(false);
int T; cin >> T; while (T--) {
int maxt = 0, mint = 0;
int l, n; cin >> l >> n;
for (int i = 0; i < n; ++i) {
int x; cin >> x;
mint = max(mint, min(l - x, x));
maxt = max(maxt, max(l - x, x));
}
cout << mint << " " << maxt << endl;
}
return 0;
}
子集和问题的 DFS 算法
判断一个集合中是否存在一个和正好为 k
的子集
1、选或者不选,是两种策略;对于每一个集合中的元素,选或不选,共有
2n
2
n
种情况。
2、枚举所有情况,判断子集的和是否为 k
3、若决策到了第 i
个元素,其组成的子集已经超过了 k
,可以停止继续该分支的枚举。
#include <iostream>
using namespace std;
const int maxn = 100 + 5;
int n, k, a[maxn];
void input() {
cin >> n >> k;
for (int i = 0; i < n; ++i) cin >> a[i];
}
bool dfs(int i, int sum) {
if (i == n) return sum == k;
return dfs(i + 1, sum) || dfs(i + 1, sum + a[i]);
}
int main() {
ios::sync_with_stdio(false);
input();
cout << dfs(0, 0) << endl;
return 0;
}
POJ-2386
判断一个矩阵中有多少连通的块。
1、DFS 进入一个块中,标记该小块已经被访问过
2、若其邻居小块也是块且未被访问过,DFS 进去
3、一次 DFS 结束后一个大块就被标记过,并且大块与其他大块不连通,可根据此统计总块数
#include <iostream>
using namespace std;
const int maxn = 100+5;
char rect[maxn][maxn];
int N, M;
void init() {
cin >> N >> M;
for (int i = 0; i < N; ++i) scanf("%s", rect[i]);
}
void dfs(int i, int j) {
rect[i][j] = '.';
for (int dx = -1; dx <= 1; ++dx) {
for (int dy = -1; dy <= 1; ++dy) {
int nx = i + dx;
int ny = j + dy;
if (nx >= 0 && nx < N && ny >= 0 && ny < M && rect[nx][ny] == 'W') dfs(nx, ny);
}
}
}
int main() {
ios::sync_with_stdio(false);
init();
int re = 0;
for (int i = 0; i < N; ++i) for (int j = 0; j < M; ++j) if (rect[i][j] == 'W') { dfs(i, j); re++; }
cout << re << endl;
return 0;
}
迷宫的最短路径
求迷宫从起点 sx
,sy
到终点 gx
,gy
的距离
1、BFS,标记被访问过的店,新的被访问的点距离起点的距离为旧的点的距离 +1
2、下面的做法体现不出层次感,只是将老的点拿出来,新的点放进去。不够优美
#include <iostream>
#include <queue>
using namespace std;
const int INF = 100000000;
typedef pair<int, int> P;
const int maxn = 1000 + 6;
char maze[maxn][maxn];
int n, m; int sx, sy; int gx, gy;
int d[maxn][maxn];
int dx[4] = { 1,0,-1,0 }, dy[4] = { 0,1,0,-1 };
void input() { }
int bfs() {
queue<P> que;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) d[i][j] = INF;
}
que.push(P(sx, sy));
d[sx][sy] = 0;
while (que.size()) {
P p = que.front(); que.pop();
if (p.first == gx && p.second == gy) break;
for (int i = 0; i < 4; ++i) {
int nx = p.first + dx[i], ny = p.second + dy[i];
if (0 <= nx && nx < n && 0 <= ny && ny < m && maze[nx][ny] != '#' && d[nx][ny] == INF) {
que.push(P(nx, ny));
d[nx][ny] = d[p.first][p.second] + 1;
}
}
}
return d[gx][gy];
}
int main() {
ios::sync_with_stdio(false);
input();
int res = bfs();
return 0;
}
下面的 level_bfs
清晰地 BFS 了整个迷宫。在第一个 while
中判断是否结束,并得到当前层节点的数量。在第二个 while
中完整取出这一层的所有节点,如此可以对每一层做特殊处理,不需要对其进行额外的标记,非常简洁优美。同样适用于二叉树的层次遍历。
int level_bfs() {
int len = 0;
queue<P> que;
while (que.size()) {
len++;
int size = que.size();
while (size--) {
P p = que.front(); que.pop();
if (p.first == gx && p.second == gy) return len;
for (int i = 0; i < 4; ++i) {
int nx = p.first + dx[i], ny = p.second + dy[i];
if (0 <= nx && nx < n && 0 <= ny && ny < m && maze[nx][ny] != '#' && d[nx][ny] == 0) {
que.push(P(nx, ny));
d[nx][ny] = 1;
}
}
}
}
return len;
}
贪心水题 POJ-3617
1、从字符串的左边和右边同时考量,选取两个中的最小值
2、若左右相等,则选取他们各自的下一个进行比较
#include <cstdio>
#include <iostream>
using namespace std;
int n;
const int maxn = 2000 + 5;
char s[maxn];
void solve() {
int k = 0;
int a = 0, b = n - 1;
while (a <= b) {
bool left = false;
for (int i = 0; a + i <= b; ++i) {
if (s[a + i] < s[b - i]) {
left = true;
break;
}
else if (s[a + i] > s[b - i]) {
left = false;
break;
}
}
if (left) putchar(s[a++]);
else putchar(s[b--]);
if (++k == 80) { putchar('\n'); k = 0; }
}
putchar('\n');
}
int main() {
ios::sync_with_stdio(false);
while (cin >> n) {
for (int i = 0; i < n; ++i) cin >> s[i];
solve();
}
return 0;
}
贪心水题 POJ-2069 Saruman’s Army
选取最少的线段覆盖所有的点,线段的中点必须在某个点上
1、从左到右贪心选择距离最左边距离最远且小于等于 R
的点,再从这个点贪心选择距离最远且小于 R
的点。计入一次结果。
2、再从下一个点重复执行过程
1
1
。
#include <algorithm>
#include <iostream>
using namespace std;
const int maxn = 1000 + 5;
int x[maxn];
int main() {
ios::sync_with_stdio(false);
int r, n;
while (cin >> r >> n && (r != -1 && n != -1)) {
for (int i = 0; i < n; ++i) cin >> x[i];
sort(x, x + n);
int i = 0, ans = 0;
while (i < n) {
int s = x[i++];
while (i < n && x[i] <= s + r) ++i;
int p = x[i - 1];
while (i < n && x[i] <= p + r) ++i;
ans++;
}
cout << ans << endl;
}
return 0;
}
贪心水题 POJ-3253 Fence Repair
求切割木板的最小消耗值
1、切割的消耗值为当前木板长度的值
2、越长的木板放在最后切割代价就越大,因为从一个木板中把最小的切去和把最大的切去其消耗相同,而如果把最小的切去,下次切就要切包含最大的那块的大木板,就要将大木板代价算进去。
假设存在两种不同的情况,分别按照从小到大排序和从大到小排序,通过计算可得,从大到小选取木板进行切割可以得到最优解。
3、即小的木板会被多次计算,这个模型符合哈夫曼编码问题,套用哈夫曼求解。
#include <functional>
#include <iostream>
#include <queue>
using namespace std;
int n;
int main() {
ios::sync_with_stdio(false);
while (cin >> n) {
int x;
priority_queue<int, vector<int>, greater<int>> pq;
while (n--) {
cin >> x;
pq.push(x);
}
int ans = 0;
while (pq.size() > 1) {
int a = pq.top(); pq.pop();
int b = pq.top(); pq.pop();
ans += a + b;
pq.push(a + b);
}
cout << ans << endl;
}
return 0;
}