动态规划

状压DP

  • 集合枚举 \(O(3^n)\)
    \(S\) 是一个二进制数,表示一个集合,\(S0\) 表示 \(S\) 的子集。
code
//包括S本身
for (int S = 1; S < (1 << n); S ++ )
    for (int S0 = S; S0; S0 = (S0 - 1) & S)
//不包括S本身
for (int S = 1; S < (1 << n); S ++ )
    for (int S0 = (S - 1) & S; S0; S0 = (S0 - 1) & S)

斯坦纳树

  • 问题:给定连通图 \(G\) 中的 \(n\) 个点与 \(k\) 个关键点,连接 \(k\) 个关键点,使得生成树的所有边的权值和最小。
  • 状态表示 :设 \(dp(i,S)\) 表示以 \(i\) 为根的一棵树,包含集合 \(S\) 中所有点的最小代价。
  • 状态转移:
    一棵以 \(i\) 为根的树有两种情况,第一种是 \(i\) 的度数为 \(1\),另一种是 \(i\) 的度数大于 \(1\)
    对于 \(i\) 的度数为 \(1\) 的情况,可以考虑枚举树上与 \(i\) 相邻的点 \(j\),则$$min(dp(i,S),dp(j,S)+w(j,i))→dp(i,S)$$ 对于 \(i\) 的度数大于 \(1\) 的情况,可以划分成几个子树考虑,即:

\[min(dp(i,S),dp(i,T)+dp(i,S−T))→dp(i,S) (T⊆S) \]

【模板】最小斯坦纳树
\(O(n×3^k+m\, log\, m×2^k)\)

code
#define PII pair<int, int>
#define x first
#define y second
#define mk make_pair

const int N = 510, M = 1010, INF = 0x3f3f3f3f;

int n, m, k;
int h[M], w[M], e[M], ne[M], idx;
int tr[M];
int f[N][5000], st[N];
int p[N];
priority_queue<PII, vector<PII>, greater<PII> > q;

void add(int a, int b, int c)
{
    tr[idx] = e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

void dij(int s)
{
    memset(st, 0, sizeof st);
    while (!q.empty())
    {
        PII t = q.top();
        q.pop();
        if (st[t.y]) continue;
        st[t.y] = 1;
        for (int i = h[t.y]; ~i; i = ne[i])
        {
            if (f[tr[i]][s] > f[t.y][s] + w[i])
            {
                f[tr[i]][s] = f[t.y][s] + w[i];
                q.push(mk(f[tr[i]][s], tr[i]));
            }
        }
    }
}

int main()
{
    memset(h, -1, sizeof h);
    memset(f, 0x3f, sizeof f);
    rd(n), rd(m), rd(k);
    for (int i = 1, a, b, c; i <= m; i ++ )
    {
        rd(a), rd(b), rd(c);
        add(a, b, c);
        add(b, a, c);
    }
    for (int i = 1; i <= k; i ++ )
    {
        rd(p[i]);
        f[p[i]][1 << (i - 1)] = 0;
    }
    for (int s = 1; s < (1 << k); s ++ )
    {
        for (int i = 1; i <= n; i ++ )
        {
            for (int subs = s & (s - 1); subs; subs = s & (subs - 1))
                f[i][s] = min(f[i][s], f[i][subs] + f[i][s ^ subs]);
            if (f[i][s] != INF) q.push(mk(f[i][s], i));
        }
        dij(s);
    }
    printf("%d\n", f[p[1]][(1 << k) - 1]);
    return 0;
}

例题

[WC2008]游览计划

  • 状态表示:设 \(dp(i,S)\) 表示以 \(i\) 为根的一棵树,包含集合 \(S\) 中所有点的最小点权值和。
  • 状态转移:$$min(dp(i,S),dp(j,S)+w(j,i))→dp(i,S)$$$$min(dp(i,S),dp(i,T)+dp(i,S−T)-a_i)→dp(i,S) (T⊆S)$$由于此处合并时同一个点 a_i,会被加两次,所以减去。
  • 路径记录:
    \(pre[i][s]\) 记录转移到 \(i\) 为根,连通状态集合为 \(s\) 时的点与集合的信息。在 \(DP\) 结束后从 \(pre[root][S]\) 出发,寻找与集合里的点相连的那些点并逐步分解集合 \(S\),用 \(ans\) 数组来记录被使用的那些点,当集合分解完毕时搜索也就结束了。
code
#define PII pair<int, int>
#define PPI pair<PII, int>
#define x first
#define y second
#define mk make_pair

const int N = 210, M = 2010, INF = 0x3f3f3f3f;
const int mx[4] = {0, 0, 1, -1}, my[4] = {1, -1, 0, 0};

int n, m, K, root;
int f[N][M], a[N], ans[15][15];
bool st[N];
PPI pre[N][M];
queue<PII> q;

bool ins(PII t)
{
    return 0 <= t.x && t.x < n && 0 <= t.y && t.y < m;
}

int get(PII t)
{
    return t.x * m + t.y;
}

void spfa(int S)
{
    memset(st, 0, sizeof st);
    while (!q.empty())
    {
        PII t = q.front();
        q.pop();
        st[get(t)] = 0;
        for (int i = 0; i < 4; i ++ )
        {
            PII ver = mk(t.x + mx[i], t.y + my[i]);
            int gt = get(t), gv = get(ver);
            if (ins(ver) && f[gv][S] > f[gt][S] + a[gv])
            {
                f[gv][S] = f[gt][S] + a[gv];
                if (!st[gv])
                {
                    st[gv] = 1;
                    q.push(ver);
                }
                pre[gv][S] = mk(t, S);
            }
        }
    }
}

void dfs(PII t, int S)
{
    if (!pre[get(t)][S].y) return ;
    ans[t.x][t.y] = 1;
    int gt = get(t);
    if (pre[gt][S].x == t)
        dfs(t, S ^ pre[gt][S].y);
    dfs(pre[gt][S].x, pre[gt][S].y);
}

int main()
{
    memset(f, 0x3f, sizeof f);
    rd(n), rd(m);
    int tot = 0;
    for (int i = 0; i < n; i ++ )
    for (int j = 0; j < m; j ++ )
    {
        rd(a[tot]);
        if (!a[tot])
        {
            f[tot][1 << (K ++ )] = 0;
            root = tot;
        }
        tot ++ ;
    }
    for (int S = 1; S < (1 << K); S ++ )
    {
        for (int i = 0; i < n * m; i ++ )
        {
            for (int S0 = S & (S - 1); S0; S0 = S & (S0 - 1))
            {
                if (f[i][S] > f[i][S0] + f[i][S ^ S0] - a[i])
                {
                    f[i][S] = f[i][S0] + f[i][S ^ S0] - a[i];
                    pre[i][S] = mk(mk(i / m, i % m), S0);
                }
            }
            if (f[i][S] < INF) q.push(mk(i / m, i % m));
        }
        spfa(S);
    }
    printf("%d\n", f[root][(1 << K) - 1]);
    dfs(mk(root / m, root % m), (1 << K) - 1);
    for (int i = 0, tot = 0; i < n; i ++ )
    {
        for (int j = 0; j < m; j ++ )
        {
            if (!a[tot ++ ])
                putchar('x');
                else putchar(ans[i][j] ? 'o' : '_');
        }
        if (i != n - 1) printf("\n");
    }
    return 0;
}
posted @ 2022-08-04 11:01  kroyosh  阅读(36)  评论(0)    收藏  举报