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;
}
posted @ 2020-11-09 22:07  duyiblue  阅读(604)  评论(0编辑  收藏  举报