Codeforces - 894E - Ralph and Mushrooms(Tarjan + 拓扑序 dp)

题目链接:https://codeforces.com/problemset/problem/894/E

10.1 题意

给定一个 n ( 1 ≤ n ≤ 1 0 6 ) n(1 \le n \le 10^6) n(1n106) 个点和 m ( 0 ≤ m ≤ 1 0 6 ) m(0 \le m \le 10^6) m(0m106) 条边的有向图,每条边 i i i 的权值为 w i ( 0 ≤ w i ≤ 1 0 8 ) w_i(0 \le w_i \le 10^8) wi(0wi108)。第 k k k 次经过边 i i i 会得到 max ⁡ ( 0 , w i − k ( k − 1 ) 2 ) \max(0,w_i-\frac{k(k-1)}{2}) max(0,wi2k(k1)) 的收益。

问从 s s s 出发,可以获得的最大总收益。

10.2 解题过程

容易发现,在同一个强连通分量中的边可以反复经过,因此强连通分量中的所有边都可以其最大价值。

考虑使用 Tarjan 缩点,缩点之后形成一张有向无环图。

对于这张有向无环图上的边,只能经过一次,因此只能获得 w i w_i wi 的价值。

s u m ( n ) = ∑ i = 1 n i = n ( 1 + n ) 2 sum(n)=\sum_{i=1}^n i=\frac{n(1+n)}{2} sum(n)=i=1ni=2n(1+n) p r e f i x ( n ) = ∑ i = 1 n s u m ( i ) prefix(n)=\sum_{i=1}^n sum(i) prefix(n)=i=1nsum(i)

现在我们要计算强连通分量中每一条边对答案的贡献。

设某一条边被经过了 k k k 次,则我们得到的权值之和为:

∑ i = 0 k ( w − s u m ( k ) ) \sum_{i=0}^k (w-sum(k)) i=0k(wsum(k))

其中, w − s u m ( k ) w-sum(k) wsum(k) 必须满足 w − s u m ( k ) ≥ 0 w-sum(k) \ge 0 wsum(k)0,解此一元二次方程得到 k k k 的取值范围为 [ 0 , 8 w + 1 2 ] [0, \frac{\sqrt{8w+1}}{2}] [0,28w+1 ]

因此,这条边对答案的贡献为 ( k + 1 ) ⋅ w − p r e f i x ( k ) (k+1)\cdot w - prefix(k) (k+1)wprefix(k)

设点 u u u 所属的联通分量为 c o l [ u ] col[u] col[u]

对于每一条强连通分量中的边 < u , v > <u,v> <u,v>,将其贡献加入到 a [ c o l [ u ] ] a[col[u]] a[col[u]] 中。

在进行拓扑排序之后,需要倒着跑 d p dp dp 转移。

d p [ c o l [ u ] ] dp[col[u]] dp[col[u]] 为从 u u u 这一联通分量出发可以得到的最大价值,则对于连通分量之外的边 < u , v , w > <u,v,w> <u,v,w>,有如下转移
d p [ c o l [ u ] ] = max ⁡ ( d p [ c o l [ u ] ] , d p [ c o l [ v ] ] + a [ c o l [ u ] ] + w ) dp[col[u]] = \max(dp[col[u]],dp[col[v]]+a[col[u]]+w) dp[col[u]]=max(dp[col[u]],dp[col[v]]+a[col[u]]+w)
最终的答案为 d p [ c o l [ s ] ] dp[col[s]] dp[col[s]]

时间复杂度: O ( n ) O(n) O(n)

10.3 代码

int head[maxn], n, m, cnt, tot, top, num, s;
int dfn[maxn], low[maxn], st[maxn], col[maxn], indeg[maxn], topo[maxn], pos[maxn];
bool instack[maxn];
ll a[maxn], prefix[maxn], dp[maxn];
struct edge
{
    int u, v, nxt;
    ll w;
} Edge[maxn];
void init()
{
    for(int i = 0; i <= n; i++) head[i] = -1;
    cnt = 0;
}
void addedge(int u, int v, ll w)
{
    Edge[cnt].u = u;
    Edge[cnt].v = v;
    Edge[cnt].w = w;
    Edge[cnt].nxt = head[u];
    head[u] = cnt++;
}
void tarjan(int id)
{
    dfn[id] = low[id] = ++tot;
    st[++top] = id;
    instack[id] = true;
    for(int i = head[id]; i != -1; i = Edge[i].nxt)
    {
        int v = Edge[i].v;
        if(!dfn[v])
        {
            tarjan(v);
            low[id] = min(low[id], low[v]);
        }
        else if(instack[v])
            low[id] = min(low[id], dfn[v]);
    }
    if(dfn[id] == low[id])
    {
        int v;
        num++;
        do
        {
            v = st[top--];
            instack[v] = false;
            col[v] = num;
        } while(v != id);
    }
}
void toposort()
{
    queue<int> que;
    for(int i = 1; i <= num; i++)
    {
        if(!indeg[i])   que.push(i);
    }
    int flag = 0;
    while(!que.empty())
    {
        int now = que.front();
        que.pop();
        topo[++flag] = now;
        for(int i = head[now]; i != -1; i = Edge[i].nxt)
        {
            int v = Edge[i].v;
            --indeg[v];
            if(!indeg[v])   que.push(v);
        }
    }
}
int main()
{
    int u, v;
    ll w;
    scanf("%d%d", &n, &m);
    init();
    for(int i = 1; i <= m; i++)
    {
        scanf("%d%d%lld", &u, &v, &w);
        addedge(u, v, w);
    }
    scanf("%d", &s);
    for(int i = 1; i <= n; i++)
    {
        if(!dfn[i]) tarjan(i);
    }
    init();
    ll sum = 0;
    for(int i = 1; i <= 20000; i++)
    {
        sum = sum + 1LL * i;
        prefix[i] = prefix[i - 1] + sum;
    }
    for(int i = 0; i < m; i++)
    {
        u = Edge[i].u;
        v = Edge[i].v;
        w = Edge[i].w;
        if(col[u] == col[v])
        {
            ll k = (sqrt(8.0 * w + 1.0) - 1) / 2;
            a[col[u]] += ((k + 1) * w - prefix[k]);
            continue;
        }
        else
        {
            addedge(col[u], col[v], w);
            indeg[col[v]]++;
        }
    }
    toposort();
    for(int i = 1; i <= num; i++) pos[topo[i]] = i;
    for(int i = num; i >= 1; i--)
    {
        int u = topo[i];
        dp[u] = a[u];
        for(int j = head[u]; j != -1; j = Edge[j].nxt)
        {
            int v = Edge[j].v;
            if(pos[v] > pos[u]) dp[u] = max(dp[u], dp[v] + a[u] + Edge[j].w);
        }
    }
    printf("%lld\n", dp[col[s]]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用\[1\]中提到了一种树形动态规划的方法来解决CodeForces - 982C问题。在这个问题中,subtree指的是子连通块,而不是子树。为了使cnt_white - cnt_black尽可能大,可以使用两次树形动态规划来求解。第一次是自底向上的过程,维护一个dp数组,表示以每个节点为根的子树中的最大连通块。第二次是自顶向下的过程,处理自底向上过程中无法包含的树链所代表的子树。在第二次遍历中,需要维护一个sum变量,用于存储树链所代表的子树的贡献。根据ans\[u\]的正负,决定是否能对相邻的子节点做出贡献。如果ans\[u\]为正,则减去dp\[v\]就是树链所代表的子树的权值。最终,ans\[u\]代表包含节点u在内的子连通块的最大权值。\[1\] 问题: CodeForces - 982C 树形DP是什么问题?如何解决? 回答: CodeForces - 982C是一个树形动态规划问题。在这个问题中,需要求解子连通块的最大权值和,使得cnt_white - cnt_black尽可能大。解决这个问题的方法是使用两次树形动态规划。第一次是自底向上的过程,维护一个dp数组,表示以每个节点为根的子树中的最大连通块。第二次是自顶向下的过程,处理自底向上过程中无法包含的树链所代表的子树。在第二次遍历中,需要维护一个sum变量,用于存储树链所代表的子树的贡献。根据ans\[u\]的正负,决定是否能对相邻的子节点做出贡献。最终,ans\[u\]代表包含节点u在内的子连通块的最大权值。\[1\] #### 引用[.reference_title] - *1* *2* [CodeForces - 1324F Maximum White Subtree(树形dp)](https://blog.csdn.net/qq_45458915/article/details/104831678)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值