《挑战程序设计竞赛》水题放这里

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;
}
迷宫的最短路径

求迷宫从起点 sxsy 到终点 gxgy 的距离
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、越长的木板放在最后切割代价就越大,因为从一个木板中把最小的切去和把最大的切去其消耗相同,而如果把最小的切去,下次切就要切包含最大的那块的大木板,就要将大木板代价算进去。

ans=i=1nj=ina[j]

假设存在两种不同的情况,分别按照从小到大排序和从大到小排序,通过计算可得,从大到小选取木板进行切割可以得到最优解。
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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值