[USACO Training] Section 2.4

8月份的时候计划做完Section 1 ~ 2。但是暑假结束的时候Section 2.4还差3道题,就搁置了。这两天做了一下。

TEXT Shortest Paths

最短路径。介绍了Dijkstra和Floyd-Warshall算法。

PROB The Tamworth Two

Farmer John和牛以某种方式在田地里转圈圈……问:能否相遇?最快何时相遇?
模拟。为了判定是否走进死循环,我弄了个6维数组。ANALYSIS提供了另一种思路:走400*400=160000步。

/*
ID: chrt2001
PROG: ttwo
LANG: C++
*/
#include <cstdio>
#include <algorithm>
using namespace std;
const int di[] = {-1, 0, 1, 0}, dj[] = {0, 1, 0, -1}, INF = 1<<30;
char M[12][12];
int S[12][12][4][12][12][4];

inline void move(int& i, int& j, int& k)
{
    char y = M[i + di[k]][j + dj[k]];
    if (y == '*') {
        k = (k+1) % 4;
    } else {
        i += di[k];
        j += dj[k];
    }
}

int solve(int fi, int fj, int ci, int cj)
{
    int fk = 0, ck = 0, t = 0;
    for (int *p = &S[fi][fj][fk][ci][cj][ck]; !*p; p = &S[fi][fj][fk][ci][cj][ck]) {
        if (fi == ci && fj == cj)
            return t;
        *p = t++;
        move(fi, fj, fk);
        move(ci, cj, ck);
    }
    return 0;
}

int main()
{
    freopen("ttwo.in", "r", stdin);
    freopen("ttwo.out", "w", stdout);
    for (int i = 0; i <= 11; ++i)
        M[0][i] = M[11][i] = M[i][0] = M[i][11] = '*';
    char s[11];
    int fi, fj, ci, cj;
    for (int i = 1; i <= 10; ++i) {
        scanf("%s", s);
        for (int j = 1; j <= 10; ++j) {
            M[i][j] = s[j-1];
            if (s[j-1] == 'F') {
                fi = i;
                fj = j;
            } else if (s[j-1] == 'C') {
                ci = i;
                cj = j;
            }
        }
    }
    printf("%d\n", solve(fi, fj, ci, cj));
    return 0;
}

PROB Overfencing

迷宫有两个出口。寻找迷宫里距离出口的最短路最长的点。

BFS。其实可以把出口都丢进队列一起跑。

/*
ID: chrt2001
PROG: maze1
LANG: C++
*/
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
using namespace std;
const int MAXW = 38*2+1, MAXH = 100*2+1, di[] = {-1, 1, 0, 0}, dj[] = {0, 0, -1, 1}, INF = 1<<30;
char M[MAXH][MAXW+2];
bool vis[MAXH][MAXW];
int c = 0, d[2][MAXH][MAXW];    
struct Node {
    int i, j;
};

void bfs(int i, int j)
{
    memset(vis, 0, sizeof(vis));
    vis[i][j] = true;
    d[c][i][j] = 1;
    queue<Node> Q;
    Q.push((Node){i, j});
    Node u;
    while (!Q.empty()) {
        u = Q.front();
        Q.pop();
        for (int k = 0; k < 4; ++k) {
            Node v = (Node){u.i+di[k]*2, u.j+dj[k]*2};
            if (M[u.i+di[k]][u.j+dj[k]] == ' ' && !vis[v.i][v.j]) {
                vis[v.i][v.j] = true;
                d[c][v.i][v.j] = d[c][u.i][u.j] + 1;
                Q.push(v);
            }
        }
    }
    ++c;
}

inline void check(int i, int j, int i1, int j1)
{
    if (M[i][j] == '.')
        bfs(i1, j1);
}

inline void subs(int i, int j)
{
    if (M[i][j] == ' ')
        M[i][j] = '.';
}

int main()
{
    freopen("maze1.in", "r", stdin);
    freopen("maze1.out", "w", stdout);
    int W, H;
    scanf("%d %d ", &W, &H);
    W = W*2+1;
    H = H*2+1;
    for (int i = 0; i < H; ++i)
        fgets(M[i], sizeof(char)*(W+2), stdin);
    for (int i = 1; i < H; i += 2) {
        subs(i, 0);
        subs(i, W-1);
    }
    for (int j = 1; j < W; j += 2) {
        subs(0, j);
        subs(H-1, j);
    }

    for (int i = 1; i < H; i += 2) {
        check(i, 0, i, 1);
        check(i, W-1, i, W-2);
    }
    for (int j = 1; j < W; j += 2) {
        check(0, j, 1, j);
        check(H-1, j, H-2, j);
    }
    int ans = 0;
    for (int i = 1; i < H; i += 2)
        for (int j = 1; j < W; j += 2) {
            int dist = INF;
            for (int k = 0; k < 2; ++k)
                if (d[k][i][j])
                    dist = min(dist, d[k][i][j]);
            if (dist != INF)
                ans = max(ans, dist);
        }
    printf("%d\n", ans);

    return 0;
}

PROB Cow Tours

给定一个无向非连通图,每个点有二维坐标,距离是欧几里德距离。要求在两个不连通的点之间加一条边,使得新连通块的直径最小。

用Floyd-Warshall预处理一些东西,枚举即可。新的直径要么通过新加的边,要么不通过。

我用并查集给连通块编号,ANALYSIS用的是DFS。其实都不需要,Floyd-Warshall已经计算了足够的信息供我们编号。

/*
ID: chrt2001
PROG: cowtour
LANG: C++
*/
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
const int MAX_N = 150;
const double inf = 1LL<<60;
int x[MAX_N], y[MAX_N], root[MAX_N];
double M[MAX_N][MAX_N], d[MAX_N], D[MAX_N];
char s[MAX_N+1];

inline double sq(double x)
{
    return x*x;
}

inline double dist(int a, int b)
{
    return sqrt(sq(x[a] - x[b]) + sq(y[a] - y[b]));
}

int get_root(int x)
{
    return x == root[x] ? x : root[x] = get_root(root[x]);
}

int main()
{
    freopen("cowtour.in", "r", stdin);
    freopen("cowtour.out", "w", stdout);
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; ++i) {
        scanf("%d %d", &x[i], &y[i]);
        root[i] = i;
    }
    for (int i = 0; i < n; ++i) {
        scanf("%s", s);
        for (int j = 0; j < n; ++j) {
            if (s[j] == '1') {
                M[i][j] = dist(i, j);
                int rx = get_root(i), ry = get_root(j);
                if (rx != ry)
                    root[rx] = ry;
            } else if (i != j)
                M[i][j] = inf;
        }
    }
    for (int k = 0; k < n; ++k)
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < n; ++j)
                M[i][j] = min(M[i][j], M[i][k] + M[k][j]);
    for (int i = 0; i < n; ++i)
        for (int j = 0; j < n; ++j)
            if (M[i][j] != inf)
                d[i] = max(d[i], M[i][j]);
    for (int i = 0; i < n; ++i) {
        int r = get_root(i);
        D[r] = max(D[r], d[i]);
    }
    double ans = inf;
    for (int i = 0; i < n; ++i)
        for (int j = 0; j < n; ++j)
            if (M[i][j] == inf)
                ans = min(ans, max(d[i] + d[j] + dist(i, j), max(D[get_root(i)], D[get_root(j)])));
    printf("%.6f\n", ans);
    return 0;
}

PROB Bessie Come Home

求无向图中某个点集里的点到终点的最短路的最小值和对应的点,保证唯一。可能有重边和自环。

由于只有52个点,Floyd-Warshall即可。

由于把'a'..'z'当作'a'..'y',WA一次。

/*
ID: chrt2001
PROG: comehome
LANG: C++
*/
#include <cstdio>
#include <cctype>
#include <algorithm>
using namespace std;
const int inf = 0x3f3f3f3f, N = 52;
int d[N][N];

template<typename T>
void relax(T& x, T v)
{
    x = min(x, v);
}

inline int idx(char c)
{
    return isupper(c) ? c-'A' : c-'a'+26;
}

int main()
{
    freopen("comehome.in", "r", stdin);
    freopen("comehome.out", "w", stdout);

    int p;
    scanf("%d", &p);

    for (int i = 0; i < N; ++i)
        for (int j = 0; j < N; ++j)
            if (i != j)
                d[i][j] = inf;

    for (int i = 0; i < p; ++i) {
        char x, y;
        int w;
        scanf(" %c %c %d", &x, &y, &w);
        x = idx(x);
        y = idx(y);
        relax(d[x][y], w);
        relax(d[y][x], w);
    }

    for (int k = 0; k < N; ++k)
        for (int i = 0; i < N; ++i)
            for (int j = 0; j < N; ++j)
                relax(d[i][j], d[i][k] + d[k][j]);

    int num, ans = inf;

    for (int i = 0; i < 25; ++i)
        if (d[25][i] < ans) {
            ans = d[25][i];
            num = i;
        }

    printf("%c %d\n", num+'A', ans);

    return 0;
}

PROB Fractions to Decimals

分数转十进制小数,循环节用括号括起来,0循环不视作循环,整数加后缀.0

模拟长除法即可。当时一下没想到。前几天学习十进制小数转二进制小数,学到乘2取整法,于是这里用了乘10取整法。在ANALYSIS的提醒下……这不就是长除法吗?

还有一种方法,可以直接计算循环节前面的部分。没有明白,但是隐约感到和 NOI 2016 Day 1 T3 有某种联系。

从这道题我们也可以得出一个结论:既约分数p/q的小数形式循环节至多有(q-1)位。

注意格式。76个字符换一行。一开始我把换行符的位置搞错,还以为看起来不对齐是因为.(占用了不同于数字的宽度。ANALYSIS里用了printf格式控制符%.*s*是输出的最大字符数。

/*
ID: chrt2001
LANG: C++
PROG: fracdec
*/
#include <cstdio>
#include <cstring>
using namespace std;
const int MAX_N = 1e5;
char buf[MAX_N+9];
int next[MAX_N], p; // 分子 -> 下一个分子

inline void in(const char* s, int d = -1)
{
    if (d == -1)
        p += sprintf(buf+p, s);
    else
        p += sprintf(buf+p, s, d);
}

inline void out()
{
    for (int i = 0; i < p; ++i) {
        if (i && i%76 == 0)
            putchar('\n');
        putchar(buf[i]);
    }
    putchar('\n');
}

int main()
{
    freopen("fracdec.in", "r", stdin);
    freopen("fracdec.out", "w", stdout);
    memset(next, -1, sizeof(next));
    int n, d;
    scanf("%d %d", &n, &d);
    int k = n/d, head;
    n -= k*d;
    in("%d.", k);
    if (!n) {
        in("0");
        out();
        return 0;
    }
    for (head = n; next[n] == -1; n = next[n] = n*10%d)
        ;
    for (int i = head; i != n; i = next[i])
        in("%d", i*10/d);
    if (n) {
        in("(");
        int i = n;
        do {
            in("%d", i*10/d);
            i = next[i];
        } while (i != n);
        in(")");
    }
    out();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值