[一本通学习笔记] 深度优先搜索与剪枝

深度优先搜索的剪枝优化还是很灵活的。但常规来说,比较通用的优化思路主要有两类。

  • 可行性剪枝
  • 最优性剪枝

需要结合题目性质进行一定的理解与探究。必要时还可以加入一些启发式的优化。

一本通上的几个例题和练习做得有点卡,代码也很丑陋。

#10018. 「一本通 1.3 例 1」数的划分

没怎么动脑子就直接dp了

#include <bits/stdc++.h>
using namespace std;

int n,k;
int f[205][9][205]; //?分成?个数 最大不超过?

int main() {
    cin>>n>>k;
    memset(f,0,sizeof f);
    f[0][0][1]=1;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=k;j++)
            for(int l=1;l<=i;l++)
                for(int t=1;t<=l;t++)
                    f[i][j][l]+=f[i-l][j-1][t];
    int ans = 0;
    for(int i=1;i<=n;i++) ans += f[n][k][i];
    cout<<ans<<endl;
}

#10019. 「一本通 1.3 例 2」生日蛋糕

加入最优性剪枝后,搜索时的枚举顺序就会对时间效率产生影响

// R[i]>R[i+1], H[i]>H[i-1]
// Sigma(R^2*H) = V
// Sigma(2RH) + Rm^2 = S
#include <bits/stdc++.h>
using namespace std;

const int inf = 0x3f3f3f3f;
int n, m, ans = inf;
void dfs(int vol, int sur, int dep, int pr, int ph) {
    if (vol < 0 || sur > ans)
        return;
    if (2 * vol / pr + sur > ans)
        return;
    if (dep == m + 1) {
        if (vol == 0)
            ans = sur;
        return;
    }
    for (int i = pr - 1; i > m - dep; --i)
        for (int j = ph - 1; j > m - dep; --j) {
            if (dep == 1)
                sur = i * i;
            dfs(vol - i * i * j, sur + 2 * i * j, dep + 1, i, j);
        }
}

int main() {
    cin >> n >> m;
    dfs(n, 0, 1, sqrt(n), n);
    if (ans == inf)
        cout << "0" << endl;
    else
        cout << ans << endl;
    return 0;
}

#10020. 「一本通 1.3 例 3」小木棍

听说有个数据点有问题?

#include <bits/stdc++.h>
using namespace std;

int n, a[105], u[105];

bool dfs(int idx, int rem) {
    if (rem < 0)
        return false;
    if (rem == 0)
        return true;
    for (int i = idx; i >= 1; --i) {
        if (u[i])
            continue;
        u[i] = 1;
        if (dfs(i - 1, rem - a[i]))
            return true;
        u[i] = 0;
    }
    return false;
}

int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    sort(a + 1, a + n + 1);
    int sum = 0;
    for (int i = 1; i <= n; i++) sum += a[i];
    for (int i = a[1]; i <= sum; i++) {
        if (sum % i)
            continue;
        memset(u, 0, sizeof u);
        int flag = 1;
        for (int j = 1; j <= sum / i; j++) {
            if (dfs(n, i) == false) {
                flag = 0;
                break;
            }
        }
        if (flag) {
            cout << i << endl;
            return 0;
        }
    }
}

#10021. 「一本通 1.3 例 4」Addition Chains

题目意思描述的不是特别清楚。自己写的时候剪枝边界没选好。

#include <bits/stdc++.h>
using namespace std;

int n, ans = 105;
vector<int> s, a;
int u[305];

void dfs(int p) {
    if (s.size() >= 11)
        return;
    if (s.size() >= ans)
        return;
    if (p == n + 1) {
        ans = min(ans, (int)(s.size()));
        if (ans == (int)(s.size()))
            a = s;
    } else
        for (int i = p; i <= min(2 * p + 1, n); i++) {
            if (u[i] == 0)
                continue;
            for (int j = 0; j < s.size(); j++) u[i + s[j]]++;
            u[2 * i]++;
            s.push_back(i);
            dfs(i + 1);
            s.pop_back();
            for (int j = 0; j < s.size(); j++) u[i + s[j]]--;
            u[2 * i]--;
        }
}

int main() {
    while (cin >> n) {
        ans = 105;
        s.clear();
        if (n == 0)
            return 0;
        s.push_back(1);
        u[1] = u[2] = 1;
        dfs(2);
        // cout<<ans<<endl;
        for (int i = 0; i < a.size(); i++) cout << a[i] << " ";
        cout << endl;
    }
}

#10022. 「一本通 1.3 练习 1」埃及分数

#include <bits/stdc++.h>
using namespace std;
#define ll long long

ll gcd(ll x, ll y) { return y ? gcd(y, x % y) : x; }
ll flag = 0;

struct frac {
    ll p, q;
    frac() { p = q = 1; }
    frac(ll x) {
        p = 1;
        q = x;
    }
    frac(ll x, ll y) {
        p = x;
        q = y;
    }
    void simp() {
        ll g = gcd(p, q);
        p /= g;
        q /= g;
    }
    void add(frac &b) {
        p = p * b.q + q * b.p;
        q *= b.q;
        simp();
    }
    void dec(frac &b) {
        p = p * b.q - q * b.p;
        q *= b.q;
        simp();
    }
} a;
ll mx = 0x3f3f3f3f;
vector<ll> sta, sans;
ll ans, D;

void dfs(ll dep, ll lim, frac rem) {
    // cout<<dep<<" "<<lim<<" "<<rem.p<<"/"<<rem.q<<endl;
    if (lim >= mx)
        return;
    if (dep == 0) {
        if (rem.p == 0) {
            flag = 1;
            mx = min(mx, lim);
            if (mx == lim)
                sans = sta;
        }
    } else if (dep == 1 && (rem.p == 1 && rem.q > lim)) {
        flag = 1;
        mx = min(mx, rem.q);
        if (mx == rem.q) {
            sans = sta;
            sans.push_back(mx);
        }
    } else {
        if (rem.p <= 0 || (rem.p > 0 && rem.q < 0))
            return;
        for (ll i = max(lim, rem.q / rem.p) + 1; i <= 100000; i++) {
            if ((dep)*rem.q <= rem.p * i)
                break;
            sta.push_back(i);
            frac tmp(i);
            rem.dec(tmp);
            dfs(dep - 1, i, rem);
            rem.add(tmp);
            sta.pop_back();
        }
    }
}

int main() {
    cin >> a.p >> a.q;
    int g = gcd(a.p, a.q);
    a.p /= g;
    a.q /= g;
    for (ll i = 1; i <= 100; i++) {
        D = i;
        dfs(i, 1, a);
        if (flag)
            break;
    }
    if (flag) {
        for (ll i = 0; i < sans.size(); i++) cout << sans[i] << " ";
        cout << endl;
    }
}

#10023. 「一本通 1.3 练习 2」平板涂色

抽象成几个点,有依赖关系的块连边。搜索枚举的时候判断应该在该点之前涂的点是否都涂过了,这样跑的比较快。如果反过来,检验所有应该在该点之后涂的点是否都被涂过,就会跑得比较慢。

#include <bits/stdc++.h>
using namespace std;

vector<int> g[15];
int a[15][15], n, x[2], y[2], c[15], s[15], u[15], cnt, ans = 0x3f3f3f3f;
int _c = 0;

void dfs(int pos) {
    ++_c;
    if (cnt >= ans)
        return;
    if (pos == n + 1)
        ans = min(ans, cnt);
    else
        for (int i = 1; i <= n; i++) {
            if (u[i])
                continue;
            int fg = 0;
            for (int j = 0; j < g[i].size(); j++)
                if (u[g[i][j]] == 0) {
                    fg = 1;
                    break;
                }
            if (fg)
                continue;
            u[i] = 1;
            s[pos] = i;
            if (c[s[pos]] - c[s[pos - 1]])
                ++cnt;
            dfs(pos + 1);
            if (c[s[pos]] - c[s[pos - 1]])
                --cnt;
            u[i] = 0;
            s[pos] = 0;
        }
}

int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> x[0] >> y[0] >> x[1] >> y[1] >> c[i];
        for (int j = x[0] + 1; j <= x[1]; j++)
            for (int k = y[0] + 1; k <= y[1]; k++) a[j][k] = i;
    }
    for (int i = 1; i <= 9; i++)
        for (int j = 1; j <= 9; j++)
            if (a[i][j] && a[i - 1][j] && a[i][j] != a[i - 1][j])
                g[a[i][j]].push_back(a[i - 1][j]);
    for (int i = 1; i <= n; i++)
        sort(g[i].begin(), g[i].end()), g[i].resize(unique(g[i].begin(), g[i].end()) - g[i].begin());

    dfs(1);

    cout << ans << endl;
    // cout<<_c<<endl;
}
#10019. 「一本通 1.3 例 2」生日蛋糕  
原文地址:https://www.cnblogs.com/mollnn/p/11602873.html