「题解 P7098」yLOI2020 凉凉

这题看到数据范围 $n\le 14$ 明示状压, 但是状压状态却相对难分析一点, 因为在思考爆搜策略时, 我们会发现, 不仅仅需要关心某一条线路或者某一个深度是否被选择, 同样也要关心哪条线路选择了哪个深度, 以便于不同线路之间合并的进行.

然而, 我们不妨考虑对阻碍状压的因素: 合并, 进行预处理, 将全部可以合并的线路 (即放在同一层不会发生冲突) 提前合并, 那么就变成了两个分别处理的基础状压 $dp$ 问题了.

合并的思路:

令 $d_{i,set}$ 表示 $set$ 集合内的所有列车放在第 $i$ 层所需的费用, 容易得到状压策略:

若新加入的列车 $j$ 与原集合 $set$ 不冲突, 即可进行状态转移:

$d_{i,set\bigcup{j}}=min{d_{i,set\bigcup{j}},d_{i,set}+sum_{j,i}}$

状压 $dp$ 的思路:

令 $f_{i,set}$ 表示 $set$ 集合内所有列车放在前 $i$ 层所需的费用, 那么我们就可以直接枚举子集来做了:

对于第$i$层每个可选的列车集合 $j$, 枚举它的子集 $k$, 令 $l=\complement_jk$, 则可以进行状态转移:

$f_{i,j} = min{f_{i,j}, f_{i-1,l} + d[i][k]}$

最后$ans=f_{i,U}$

当然, 由于状压 $dp$ 中 $4^n$ 的复杂度仍然无法接受, 所以还要用个枚举子集的 trick

for (int k = j; k; k = j & (k - 1))

这样时间复杂度就会降为 $n \times \Sigma_{i=0}^{n} C_n^i\times 2^i\times 1^{n-i}=n\times 3^n$ 了

完整代码:

(记得开long long)

#include <bits/stdc++.h>

#define int long long

using namespace std;

typedef long long ll;

const int MAXN = 16;
const int MAXS = 1 << 16;
const int MAXM = 1e5 + 5;

int n, m;
int siz[MAXN];
bitset<MAXM> sta[MAXN];
int price[MAXN][MAXM], data[MAXN][MAXM];

// sum[i][j] refer to the price of constructing all the stations for train j at the height i
int sum[MAXN][MAXN];
// d[i][j] refer to the price of constructing all the stations for trains in set(j) at the height i
int d[MAXN][MAXS];
// f[i][j] refer to the price of constructing all the stations for trains in set(j) beyond or at height i
int f[MAXN][MAXS];

signed main()
{
    memset(d, 0x3f, sizeof(d));
    memset(f, 0x3f, sizeof(f));
    scanf("%lld%lld", &n, &m);
    for (int i = 1; i <= n; i++)
    {
        d[i][0] = 0;
        for (int j = 1; j <= m; j++)
            scanf("%lld", &price[i][j]);
    }
    for (int i = 0; i < n; i++)
    {
        scanf("%lld", &siz[i]);
        for (int j = 1; j <= siz[i]; j++)
        {
            scanf("%lld", &data[i][j]);
            sta[i].set(data[i][j]);
        }
        for (int j = 1; j <= n; j++)
        {
            for (int k = 1; k <= siz[i]; k++)
                sum[j][i] += price[j][data[i][k]];
        }
    }
    int maxt = 1 << n;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 0; j < maxt; j++)
        {
            if (d[i][j] > 1e15)
                continue;
            bitset<MAXM> now;
            now.reset();
            for (int k = 0; k < n; k++)
                if ((j >> k) & 1)
                    now |= sta[k];
            for (int k = 0; k < n; k++)
            {
                if (!((j >> k) & 1) && !(now & sta[k]).count())
                {
                    int nex = j | (1 << k);
                    d[i][nex] = min(d[i][nex], d[i][j] + sum[i][k]);
                }
            }
        }
    }
    f[0][0] = 0;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 0; j < maxt; j++)
        {
            f[i][j] = f[i - 1][j];
            for (int k = j; k; k = j & (k - 1))
            {
                int res = j ^ k;
                f[i][j] = min(f[i][j], f[i - 1][res] + d[i][k]);
            }
        }
    }
    printf("%lld", f[n][maxt - 1]);
    return 0;
}