搜索题目 专项题解

搜索题目 专项题解

P4206 [NOI2005] 聪聪与可可

题意简述

给定无向图,以及聪聪和可可的初始位置。

聪聪要抓可可,聪聪先走,可可后走。聪聪一个单位时间内可以走一到两步,每次都是选择最靠近可可的点走,如果有多个距离可可相同的点则选择标号最小的。

可可单位时间只能走一步,并且等概率的选择附近相邻的点或者呆在原地不动。

问平均聪聪走多少个单位时间可以追到可可。

解题报告

考虑爆搜改记搜。

爆搜:需要记住当前聪聪的位置、可可的位置,每次枚举可可下一步走哪(也可以不走),然后让聪聪走两步,继续搜索(此处需要预处理聪聪应该往哪走)。退出条件是聪聪和可可重合 / 一步抓到 / 两步抓到。

改记搜:模板记搜,直接存状态 dp[i][j] 表示聪聪在 i 点,可可在 j 点的期望步数。

预处理跑 n 遍 bfs 得到任意两点间距离即可。

代码实现

// memsearch

const int MAXN = 1000 + 10;

int n, e, c, m;
int sdis[MAXN][MAXN];
int nxtnode[MAXN][MAXN]; // i -> j 最短路要走的下一个点

std::vector<int> G[MAXN];

void bfs(int st, int *dist) {
    static bool vis[MAXN];
    memset(vis, 0, sizeof vis);
    std::queue<int> q; q.push(st); dist[st] = 0; vis[st] = true;
    while (!q.empty()) {
        int u = q.front(); q.pop();
        vis[u] = false;
        forall (G[u], i) {
            int v = G[u][i];
            if (dist[v] > dist[u] + 1) {
                dist[v] = dist[u] + 1;
                if (!vis[v]) { q.push(v); vis[v] = true; }
            }
        }
    }
}

double dp[MAXN][MAXN];

double search(int cat, int mouse) {
    if (dp[cat][mouse] != 0) return dp[cat][mouse];
    double &ans = dp[cat][mouse];
    if (cat == mouse) return ans = 0;
    if (nxtnode[cat][mouse] == mouse) return ans = 1;
    if (nxtnode[nxtnode[cat][mouse]][mouse] == mouse) return ans = 1;
    forall (G[mouse], i) {
        int nv = G[mouse][i];
        ans += search(nxtnode[nxtnode[cat][mouse]][mouse], nv) + 1;
    } ans = (ans + 1 + search(nxtnode[nxtnode[cat][mouse]][mouse], mouse)) / (double) (G[mouse].size() + 1);  //不走的情况
    // 也可以写成
    // ans += search(nxtnode[nxtnode[cat][mouse]][mouse], nv); 
    // ans = (ans + search(nxtnode[nxtnode[cat][mouse]][mouse], mouse)) / (double) (G[mouse].size() + 1) + 1
    // 区别就在于把循环里的 +1 拿出来
    return ans;
}

int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> n >> e >> c >> m;
    rep (i, 1, e) {
        int u, v; cin >> u >> v;
        G[u].push_back(v); G[v].push_back(u);
    }
    memset(sdis, 0x3f, sizeof sdis);
    rep (i, 1, n) bfs(i, sdis[i]);
    memset(nxtnode, 0x3f, sizeof nxtnode);
    for (int u = 1; u <= n; ++u) {
        forall (G[u], i) {
            int v = G[u][i];
            for (int d = 1; d <= n; ++d) {
                if (u == d) continue;
                if (sdis[u][d] == sdis[v][d] + 1) {
                    nxtnode[u][d] = std::min(nxtnode[u][d], v);
                }
            }
        }
    } 
    cout << std::fixed << std::setprecision(3) << search(c, m) << endl;
    return 0;
}

P3205 [HNOI2010]合唱队

题意简述

imp

解题报告

手玩发现,原序列中最后一个人会到理想队伍的1位置或者n位置,倒数第二个会到2或n-1

于是设 f[l][r][0/1] 表示理想队伍 [l,r] 中的人还没有确定原序列位置的原序列方案数,且当前放置的人是 l(0)还是r(1)

转移就看看当前人和上一个人是往哪放的

代码实现

const int MAXN = 1000 + 10;
const int HA = 19650827;

int n;
int aa[MAXN];

int f[MAXN][MAXN][2];
// 设 `f[l][r][0/1]` 表示理想队伍 `[l,r]` 中的人还没有确定原序列位置的原序列方案数
// 且本次放置的人是 `l(0)` 还是 `r(1)`

int search(int l, int r, int p) {
    // [^v.........v^]
    // 当前插入^,上次插入v
    if (f[l][r][p] != -1) return f[l][r][p];
    int &ans = f[l][r][p] = 0;
    if (l == r) return ans = 1;
    if (l + 1 == r) return ans = (aa[l] < aa[r]);
    if (p == 0) {
        // [^v.........]
        if (aa[l] < aa[l + 1]) ans += search(l + 1, r, 0);
        // [^.........v]
        if (aa[l] < aa[r]) ans += search(l + 1, r, 1);
    } else {
        // [v.........^]
        if (aa[r] > aa[l]) ans += search(l, r - 1, 0);
        // [.........v^]
        if (aa[r] > aa[r - 1]) ans += search(l, r - 1, 1);
    } ans %= HA;
    return ans;
}

int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> n;
    rep (i, 1, n) cin >> aa[i];
    memset(f, -1, sizeof f);
    cout << (search(1, n, 0) + search(1, n, 1)) % HA << endl;
    return 0;
}

P2473 [SCOI2008] 奖励关

题意简述

k 次发钱,有 n 种金额(正数发钱,负数扣钱),每次等概率选一种金额发钱,可以拿也可以不拿。每种金额都有依赖,即拿一些金额必须之前拿过特定的金额。求期望最大钱数。

解题报告

一个长得比较像爆搜的记搜。

状态:当前发了 k 次,拿钱的状态如何(状压)

转移:枚举当前发哪种钱

代码实现

把数组开大了才过。。

const int MAXK = 1000 + 10;
const int MAXN = 17;

int k, n;
double aa[MAXN]; int pre[MAXN];
double dp[MAXK][(1 << MAXN) + 10];
bool vis[MAXK][(1 << MAXN) + 10];

double search(int tm, int stat) {
    if (tm == k + 1) return 0;
    if (vis[tm][stat]) return dp[tm][stat];
    double &ans = dp[tm][stat]; vis[tm][stat] = true;
    for (int i = 1; i <= n; ++i) {
        if ((stat & pre[i]) == pre[i]) ans += std::max(search(tm + 1, stat | (1 << (i - 1))) + aa[i], search(tm + 1, stat)) / (double) n;
        else ans += search(tm + 1, stat) / (double) n;
    } 
    return ans;
}

int main() {
    k = read(); n = read();
    for (int i = 1; i <= n; ++i) {
        aa[i] = read(); int fx = read();
        while (fx != 0) {
            pre[i] |= (1 << (fx - 1));
            fx = read();
        }
    }
    printf("%0.6lf", search(1, 0));
    return 0;
}
上一篇:力扣题10正则表达式匹配(困难)


下一篇:B 题3-2 单分支选择结构:寻找第二小数