ARC078F

发布时间 2023-11-02 21:57:54作者: lalaouye

这是一道容易想假的题(个人认为)。

首先有一个转化,我们发现直接删边不好做,我们考虑如果已经知道这条唯一路径该怎么做。我们画完图后发现,保留的图一定会是路径中的点挂着若干个路径以外的点,并且这些点可以任意互相连边,路径中不同点挂着的点集一定不能互相连边。
但是直接枚举这条路径会超时,这时候我们发现,除了这条路径以外的点的连边情况并不受路径内的连边顺序所影响,比如说 1->2->3->41->3->2->4,他们即使顺序不同,但是对答案不会产生影响,因为我们实质上关系的是这条路径上的点集,而不是这条路径。这时就很容易想到 dp 了,处理顺序也是容易的,我们设 \(f_{S,i}\) 表示当前已经处理完 \(1\)\(i\) 路径上 \(S\) 内的点的最大边权和,然后我们转移时需枚举一个点集 \(T\) 和新的唯一路径末尾 \(j\)\(j\in T\),意思是 \(j\) 挂着的点集为 \(T\),为了方便处理让 \(j\) 也属于这个集合。那么状态转移方程式为:

\[f_{S\cup T,j}\leftarrow f_{S,i}+Gat_T+e_{i,j} \]

其中 \(Gat\) 表示一个集合内部连边产生的贡献,可以预处理,\(e\) 为边权。

一定要注意 dp 的初始化。

代码(我想代码应该不难写):

auto main () -> int
{
    n = rd (), m = rd ();
    rep (i, 1, m) { int u = rd (), v = rd (), w = rd (); e[u][v] = e[v][u] = w; ret += w; }
    int maG = (1 << n) - 1; rep (S, 1, maG)
    { ve.clear (); rep (j, 0, n - 1) if (S >> j & 1) ve.eb (j + 1);
        for (auto u : ve) for (auto v : ve) { Gat[S] += e[u][v]; } Gat[S] >>= 1; 
    }  rep (S, 1, maG) rep (i, 1, n) f[S][i] = -1e12;
    rep (S, 1, maG) if (S & 1) f[S][1] = Gat[S]; rep (S, 1, maG - 1)
    { if (! (S & 1)) continue; if (S >> (n - 1)) continue; int Ga = maG ^ S;
        rep (i, 0, n - 1)
        { if (! (S >> i & 1)) continue;
            for (int T = Ga; T; T = (T - 1) & Ga)
                rep (j, 0, n - 1) if (T >> j & 1)
                        if (e[i + 1][j + 1])
                            f[S | T][j + 1] = max (f[S | T][j + 1], f[S][i + 1] + Gat[T] + e[i + 1][j + 1]);
        }
    } printf ("%lld\n", ret - f[(1 << n) - 1][n]);
}