CF1430G Yet Another DAG Problem
题目来源:CodeForces, Educational Round 96, edu96, CF1430G Yet Another DAG Problem
题目大意
给定一张 \(n\) 个点 \(m\) 条边的有向无环图(DAG),第 \(i\) 条边从 \(x_i\) 通向 \(y_i\),且有一个边权 \(w_i\)。
现在请你给每个点 \(v\) 构造一个整数点权 \(a_v\) (\(0\leq a_v\leq 10^9\))。对每条边 \(i\),我们设 \(b_i = a_{x_i} - a_{y_i}\)。你构造的点权必须满足:
- 所有 \(b_i\) 都是正数。
- 最小化 \(\sum_{i = 1}^{m}w_ib_i\)。
请给出构造方案。可以证明方案一定存在。
数据范围:\(1\leq n\leq 18\),\(1\leq m\leq\frac{n(n-1)}{2}\),\(1\leq x_i,y_i\leq n\),\(1\leq w_i\leq10^5\)。保证无自环,无重边。
本题题解
记 DAG 的节点集合为 \(V\),边集为 \(E\)。
考虑给 DAG 分层。满足对所有 \((x\to y)\in E\),\(x\) 的层数小于 \(y\) 的层数。每层点权相同。层数越大,点权越小。确定分层后,我们很容易按层数从大到小,推出每个点的点权。
分层的过程可以用一个状压 DP 来实现。设 \(f(S)\) 表示考虑了前若干层,包含了 \(S\) 里的这些节点。
转移时考虑下一层有哪些节点,设为 \(T\)。首先,\(T\) 与 \(S\) 交一定为空。第二,所有能到达 \(T\) 中至少一个点的点,都已经出现在了 \(S\) 中,即:\(\forall u \in T,\forall (v\to u)\in E:v\in S\)。枚举所有这样的 \(T\),从 \(f(S)\) 转移到 \(f(S\cup T)\)。
考虑转移的系数。所有从 \(S\) 连出的边,若终点不在 \(S\) 里,则至少都在下一层。所以转移的代价是:\(\displaystyle \sum_{(u\to v)\in E,u\in S,v\notin S} w_{u,v}\)。也就是说,我们把一条边的代价,分摊到了它跨过的每一层里:它每跨过一层(转移一次),代价就增加 \(w\)。
因为 \(T\) 是 \(S\) 的补集的子集,枚举所有 \(T\) 的时间复杂度之和是 \(O(3^n)\) 的。我们预处理出:(1) 每个点集 \(S\) 的转移代价;(2) 每个点集 \(S\) 的入点集合(因为要判断 \(T\) 是否属于该集合)。总时间复杂度 \(O(2^nn+3^n)\)。
参考代码
// problem: CF1430G
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }
const int MAXN = 18;
const ll LL_INF = 1e18;
inline int lowbit(int x) { return x & (-x); }
int n, m, w[MAXN + 5][MAXN + 5];
int id[1 << MAXN];
ll oute_sumw[1 << MAXN];
int ine[MAXN + 5], ine_union[1 << MAXN];
ll dp[1 << MAXN];
int frm[1 << MAXN];
int ans[MAXN + 5];
void get_ans(int mask, int cur_val) {
if (!mask)
return;
int pre_mask = frm[mask];
int new_add = (mask ^ pre_mask);
for (int i = new_add; i; i -= lowbit(i)) {
ans[id[lowbit(i)]] = cur_val;
}
get_ans(pre_mask, cur_val + 1);
}
int main() {
cin >> n >> m;
for (int i = 1; i <= m; ++i) {
int u, v, _w;
cin >> u >> v >> _w;
w[u][v] = _w;
ine[v] |= (1 << (u - 1));
}
for (int i = 1; i <= n; ++i) id[1 << (i - 1)] = i;
for (int i = 1; i < (1 << n); ++i) {
int u = id[lowbit(i)];
ll sum_from_u = 0;
ll sum_from_i_to_u = 0;
for (int j = 1; j <= n; ++j) {
if ((i >> (j - 1)) & 1)
sum_from_i_to_u += w[j][u];
else
sum_from_u += w[u][j];
}
oute_sumw[i] = (oute_sumw[i - lowbit(i)] + sum_from_u - sum_from_i_to_u);
ine_union[i] = (ine_union[i - lowbit(i)] | ine[u]);
}
for (int i = 1; i < (1 << n); ++i) {
dp[i] = LL_INF;
}
for (int i = 0; i < (1 << n) - 1; ++i) {
if (dp[i] == LL_INF)
continue;
int rest = (((1 << n) - 1) ^ i);
for (int j = rest; j; j = ((j - 1) & rest)) {
// 下一层: j
if ((ine_union[j] & i) == ine_union[j]) {
// j 的所有入点都在 i 中
if (dp[i + j] > dp[i] + oute_sumw[i]) {
dp[i + j] = dp[i] + oute_sumw[i];
frm[i + j] = i;
}
}
}
}
get_ans((1 << n) - 1, 1);
for (int i = 1; i <= n; ++i)
cout << ans[i] << " \n"[i == n];
return 0;
}