杂题集萃[4]

题目描述

可爱的ZYB来到一个售货机前。售货机里有一共有\(N(N≤10^5)\)
个物品,每个物品有\(A_i\)个。

自然,还有\(N\)个购买按钮。

正常情况下,按下第\(i\)个按钮,需要支付\(C_i\)的钱,然后会跳出一份物品\(i\)

如果该物品卖完了,按下此按钮无效但是,这台售货机的电路连接出了点问题。

\(i\)个按钮的“弹出电路”连向了物品\(f_i\)

假设按下了第\(i\)个按钮,售货机会按以下逻辑执行:

  1. 判断第\(i\)个物品是否为空。

  2. 如果是,不执行任何操作,退出该购买程序。

  3. 否则,要求支付\(C_i\)的钱。

  4. 因为电路坏了,实际弹出的物品会是\(f_i\)

注意:如果物品\(f_i\)为空,显然也不会有物品弹出。

ZYB很快发现了售货机的秘密,并精确掌握了\(f_i\)的值。

他又去调查了每一种物品的市场价。即他可以以\(D_i\) 的价格卖掉物品\(i\)

现在ZYB他想通过这台售货机,赚尽量多的钱。

假设ZYB有足够多的成本钱。

输入描述:

接下来有\(N\)行,每行有四个数\(f_i\),\(C_i\), \(D_i\), \(A_i\),意义同上。

输出描述:

输出一个数表示最大获利。

  • 输入

3
2 2 3 8
3 1 5 6
1 4 4 7
  • 输出

39

数据范围:

前30%:𝑁 ≤ 10

前50%: 𝑁 ≤ 200

另有10%:𝑓𝑖 = 𝑖

另有10%:𝑓𝑖 ≤ 𝑖

另有10%:𝑎𝑖 = 1

100% :1 ≤ 𝑁 ≤ \(10^5\), 1 ≤ 𝑓𝑖 ≤ 𝑁, 𝐶𝑖 ≤ 𝐷𝑖, 1 ≤ 𝐶𝑖, 𝐷𝑖, 𝐴𝑖 ≤ \(10^6\)

题解

一般情况下我们连成的有向图是基环内向树。

考虑这个环,如果存在 i 点,按下后,获得的利益\((C_i)\)不如一条连向\(f_i\)的数边。

我们就可以吧这条环断开。

否则,环上的边一定是最大的。

我们先把环上的物品取得只剩一个。

此时,一定有一个物品会取不到环边的价值。

那我们就找一个环边价值减去树边价值最小的点 i ,强制它不能选环边。

接下来统计答案。

实际上,我们只要记录到每个节点的最大和次大的边。

DFS过程中,默认接上最大边的值,维护最大边和次大边的差的最小值。

找到环之后,我们显然要断掉环——使连向某个点的边改变

(从默认的最大变成次大),此时只要减去维护的最小值即可。

复杂度\(O(n)\)

code

#include <cstdio>
#include <algorithm>

const int N=100005;
int n,idx,mn,f[N],c[N],d[N],w[N],a[N],mx[N],secmx[N],dfn[N];
long long ans;

void dfs(int x) {
    if(dfn[x]==idx) {
        ans-=mn;
        return;
    }
    if(dfn[x]) return;
    dfn[x]=idx;
    if(mx[x]) {
        ans+=1LL*w[mx[x]]*a[x];
        mn=std::min(mn,w[mx[x]]-w[secmx[x]]);
        if(mx[x]^x) dfs(mx[x]);
    }
}
int main() {
    scanf("%d",&n);
    for(int i=1;i<=n;++i) scanf("%d%d%d%d",&f[i],&c[i],&d[i],&a[i]);
    for(int i=1;i<=n;++i) {
        w[i]=d[f[i]]-c[i];
        if(w[i]<0) continue;
        if(w[i]>w[mx[f[i]]]) {
            secmx[f[i]]=mx[f[i]];
            mx[f[i]]=i;
        } else if(w[i]>w[secmx[f[i]]]) {
            secmx[f[i]]=i;
        }
    }
    for(int i=1;i<=n;++i) if(!dfn[i]) mn=(1<<30),idx++,dfs(i);
    printf("%lld\n",ans);
    return 0;
}

posted @ 2018-09-16 15:30  Sparks_Pion  阅读(153)  评论(0编辑  收藏  举报