杂题集萃[4]
题目描述
可爱的ZYB来到一个售货机前。售货机里有一共有\(N(N≤10^5)\)
个物品,每个物品有\(A_i\)个。
自然,还有\(N\)个购买按钮。
正常情况下,按下第\(i\)个按钮,需要支付\(C_i\)的钱,然后会跳出一份物品\(i\)。
如果该物品卖完了,按下此按钮无效但是,这台售货机的电路连接出了点问题。
第\(i\)个按钮的“弹出电路”连向了物品\(f_i\)。
假设按下了第\(i\)个按钮,售货机会按以下逻辑执行:
-
判断第\(i\)个物品是否为空。
-
如果是,不执行任何操作,退出该购买程序。
-
否则,要求支付\(C_i\)的钱。
-
因为电路坏了,实际弹出的物品会是\(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;
}