[USACO Training] Section 2.2

本文探讨了数据结构选择的原则,并详细介绍了二叉搜索树、哈希表、Trie树及堆的应用场景。此外,还提供了动态规划、子集求和、特定数字查找及状态验证等问题的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

TEXT Data Structures

不能为了用数据结构而用数据结构……算法有这样的需求,如:找最大、动态维护集合、判重……才去想可用的东西,如:堆、BST、哈希表。还有STL。

数据结构的选择首先看是否能工作(和问题是否配套、空间有没有爆),其次看编码和调试的难易,最后才是时间复杂度。

这篇文章介绍了二叉搜索树、哈希表、Trie、堆。作者建议我们写朴素的二叉搜索树,而非Treap、Splay等等,如果担心数据很糟糕就打乱顺序插入。是个好办法,不过个人认为BST写起来也不太麻烦。

TEXT Dynamic Programming

动态规划。

PROB Preface Numbering

告诉你罗马数字的书写规则,求1~n中分别出现了多少个I、V、X、L、C、D、M,1 <= n < 3500。
有点麻烦的是4、9要用减法表示的特殊规则,我特判了一下。

/*
ID: chrt2001
PROG: concom
LANG: C++
*/
#include <cstdio>
#include <queue>
#include <algorithm>
#include <cstring>
using namespace std;
const int MAX_N = 100, MAX_P = 50;
bool control[MAX_N+1][MAX_N+1];
int n, M[MAX_N+1][MAX_N+1], p[MAX_N+1];
queue<int> Q;

void search(int x)
{
    memset(p+1, 0, sizeof(int)*n);
    Q.push(x);
    control[x][x] = true;
    int u;
    while (!Q.empty()) {
        u = Q.front();
        Q.pop();
        for (int v = 1; v <= n; ++v)
            if (M[u][v]) {
                p[v] += M[u][v];
                if (p[v] > MAX_P && !control[x][v]) {
                    Q.push(v);
                    control[x][v] = true;
                }
            }
    }
}

int main()
{
    freopen("concom.in", "r", stdin);
    freopen("concom.out", "w", stdout);
    int m;
    scanf("%d", &m);
    for (int i = 0; i < m; ++i) {
        int x, y, p;
        scanf("%d %d %d", &x, &y, &p);
        n = max(n, max(x, y));
        M[x][y] += p;
    }
    for (int i = 1; i <= n; ++i)
        search(i);
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= n; ++j)
            if (control[i][j] && i != j)
                printf("%d %d\n", i, j);
    return 0;
}

PROB Subset Sums

将{1, 2, …, n}划分为两个集合,使得和相等。

等价于取{1, 2, …, n}的一个子集,使得和为n(n+1)/4。规定空集的和为0。令f[i][j] = {1, 2, …, i}中和为j的子集数,建立递推。

/*
ID: chrt2001
PROG: subset
LANG: C++
*/
#include <cstdio>
using namespace std;
long long f[40][391];
int main()
{
    freopen("subset.in", "r", stdin);
    freopen("subset.out", "w", stdout);
    int n;
    scanf("%d", &n);
    int sum = n*(n+1)/2;
    if (sum & 1) {
        puts("0");
        return 0;
    }
    sum /= 2;
    f[0][0] = f[1][0] = f[1][1] = 1;
    for (int i = 2; i <= n; ++i)
        for (int j = 0; j <= sum; ++j)
            f[i][j] = f[i-1][j] + (j >= i ? f[i-1][j-i] : 0);
    printf("%lld\n", f[n][sum]/2);
    return 0;
}

PROB Runaround Numbers

寻找第一个大于M且具有如下性质的数:
1. 各个位互不相同且不含0。
2. 从最高位开始,这一位是几就往右挪几步,挪到最低位“右边”是最高位,会停在某一位,从这一位开始,重复这一步骤。要求最后能回到最高位,且每一位都曾作为起点。

题目只说数据保证答案在unsigned long long范围,其实,注意到第一个性质,答案最多是9位数。我们甚至可以枚举了:9! = 362880。现阶段枚举不超过11个元素的全排列是可接受的,只要check不是很繁琐:11! = 39916800。

一开始读错了题,不知道要求每一位都要作一次起点,于是又套一个递归在间隙里填数……

/*
ID: chrt2001
PROG: runround
LANG: C++
*/
#include <cstdio>
#include <algorithm>
using namespace std;
const int p[9] = {1, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8}, INF = 1<<30;
int m, ans = INF, a[9];
bool used[10];

// 总共的位数,已经填了的位数,现在考虑的数位,现在的数值
void solve(int d, int n, int k, int x)
{
    if (x >= ans)
        return;
    if (a[k]) {
        if (k == 0 && n == d && x > m)
            ans = min(ans, x);
        return;
    }
    for (int i = 1; i <= 9; ++i)
        if (!used[i]) {
            a[k] = i;
            used[i] = true;
            solve(d, n+1, (k+i)%d, x+i*p[d-1-k]);
            used[i] = false;
        }
    a[k] = 0;
}

int main()
{
    freopen("runround.in", "r", stdin);
    freopen("runround.out", "w", stdout);
    int d = 0;
    scanf("%d", &m);
    for (int i = m; i; i /= 10)
        ++d;
    for (int i = max(2, d); i <= 9; ++i)
        for (int j = 1; j <= 9; ++j) {
            if (j%i == 0)
                continue;
            a[0] = j;
            used[j] = true;
            solve(i, 1, j%i, j*p[i-1]);
            if (ans != INF) {
                printf("%d\n", ans);
                return 0;
            }
            used[j] = false;
        }
    return 0;
}

PROB Party Lamps

之前作为穷举的例题在TEXT里讲过。

我枚举了最终状态,验证是否可行。ANALYSIS是减少按开关的次数后搜的,做的比我更好的地方是,还注意到灯的状态每隔6一循环。值得学习。

/*
ID: chrt2001
PROG: lamps
LANG: C++
*/
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAX_N = 100;
bool on[MAX_N], off[MAX_N];
char st[16][MAX_N+1];
int n, od[16], top;
inline bool cmp(int i, int j)
{
    return strcmp(st[i], st[j]) < 0;
}

void add(int x)
{
    int a;
    for (int i = 0; i < n; ++i) {
        a = 1;
        if (x & 0x8)
            a ^= 1;
        if ((x & 0x4) && (i & 0x1))
            a ^= 1;
        if ((x & 0x2) && !(i & 0x1))
            a ^= 1;
        if ((x & 0x1) && i % 3 == 0)
            a ^= 1;
        if (on[i] && !a || off[i] && a)
            return;
        st[top][i] = '0' + a;
    }
    od[top] = top++;
}

int main()
{
    freopen("lamps.in", "r", stdin);
    freopen("lamps.out", "w", stdout);
    int c, x;
    scanf("%d %d", &n, &c);
    while ((~scanf("%d", &x)) && (~x))
        on[x-1] = true;
    while ((~scanf("%d", &x)) && (~x))
        off[x-1] = true;
    int cnt;
    for (int i = 0; i < 16; ++i) {
        cnt = 0;
        for (int j = i; j; j >>= 1)
            if (j & 0x1)
                ++cnt;
        if (c >= cnt && !(c-cnt & 0x1))
            add(i);
    }
    if (top == 0)
        puts("IMPOSSIBLE");
    else {
        sort(od, od+top, cmp);
        for (int i = 0; i < top; ++i)
            printf("%s\n", st[od[i]]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值