• 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) ((0leq a_vleq 10^9))。对每条边 (i),我们设 (b_i = a_{x_i} - a_{y_i})。你构造的点权必须满足:

    • 所有 (b_i) 都是正数。
    • 最小化 (sum_{i = 1}^{m}w_ib_i)

    请给出构造方案。可以证明方案一定存在。

    数据范围:(1leq nleq 18)(1leq mleqfrac{n(n-1)}{2})(1leq x_i,y_ileq n)(1leq w_ileq10^5)。保证无自环,无重边。

    本题题解

    记 DAG 的节点集合为 (V),边集为 (E)

    考虑给 DAG 分层。满足对所有 ((x o y)in E)(x) 的层数小于 (y) 的层数。每层点权相同。层数越大,点权越小。确定分层后,我们很容易按层数从大到小,推出每个点的点权。

    分层的过程可以用一个状压 DP 来实现。设 (f(S)) 表示考虑了前若干层,包含了 (S) 里的这些节点。

    转移时考虑下一层有哪些节点,设为 (T)。首先,(T)(S) 交一定为空。第二,所有能到达 (T) 中至少一个点的点,都已经出现在了 (S) 中,即:(forall u in T,forall (v o u)in E:vin S)。枚举所有这样的 (T),从 (f(S)) 转移到 (f(Scup T))

    考虑转移的系数。所有从 (S) 连出的边,若终点不在 (S) 里,则至少都在下一层。所以转移的代价是:(displaystyle sum_{(u o v)in E,uin S,v otin 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] << " 
    "[i == n];
    	return 0;
    }
    
  • 相关阅读:
    国外摄影网站
    网络基础之子网划分
    Java-多线程第三篇3种创建的线程方式、线程的生命周期、线程控制、线程同步、线程通信
    Java-多线程第二篇多线程相关认识(2)
    设计模式-第八篇之桥接模式
    设计模式-第七篇之门面模式
    设计模式-第六篇之策略模式
    设计模式-第五篇之命令模式
    设计模式-第四篇之代理模式
    设计模式-第九篇之观察者模式
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/13951189.html
Copyright © 2020-2023  润新知