CF1266D - Decreasing Debts(构造性算法 + 数据结构 + 贪心 + 数学规律 / 铁牌级)
1266D - Decreasing Debts(源地址自⇔CF1266D)
tag
⇔构造性算法、⇔数据结构、⇔贪心、⇔数学规律、⇔铁牌级(*2000)
题意
场上有 \(n\) 个人与 \(m\) 条欠债关系( \(u_i,v_i,w_i\) 代表 \(u\) 借给了 \(v\) \(w\) 元),由于被借钱的人也会再向其他人借钱,就会形成一个异常复杂的借钱网络。我们都知道,债务关系是可以转移的,现在,请你简化这个网络,使得简化后的网络涉及的金额达到最小。输出任意一个简化后的网络。
思路
自己思路(错误)
刚开始理所当然的想到图论的解法,即简化路径。首先想到并查集,但是由于无法确定一个绝对的祖先节点(不欠钱的人),所以无法处理,后来又转而向到了 \(\tt{Tarjan}\) 的缩点,但实际上关系也不大(会建立新的欠债关系)。
金老师思路(贪心)
我们可以准确的求出某个人到底是欠了钱还是借出了钱,以及金额是多少——那么,重新、任意地链接这些人,的出来的新网络必然是最优的(用欠债人的钱还借出了钱的人)。
邦神补充:就好像打麻将时,输了钱的人直接将钱放桌上,而不必直接算清,赢了钱的人去任意的桌上拿走自己赢了的金额即可。
AC代码(贪心,元组)
备注:代码使用的元组的某些技巧涉及到C++17的新性质,同样可以使用结构体解,此处不再另写一份。
点击查看代码
//====================
LL num, a[MAX];
bool Ans;
stack<pair<LL, LL> > los, win;
vector<tuple<LL, LL, LL> > ans;
LL u, v, w, n, m;
//====================
void Solve() {
cin >> n >> m;
FOR(i, 1, m) {
cin >> u >> v >> w;
a[u] -= w;
a[v] += w;
}
FOR(i, 1, n) {
if(a[i] > 0) los.push({i, a[i]});
else if(a[i] < 0) win.push({i, -a[i]});
}
while(!win.empty()) {
pair<LL, LL> x = win.top();
win.pop();
pair<LL, LL> y = los.top();
los.pop();
if(x.second > y.second) {
ans.push_back({x.fi, y.fi, y.se});
win.push({x.fi, x.se - y.se});
}else if(x.se < y.se) {
ans.push_back({x.fi, y.fi, x.se});
los.push({y.fi, y.se - x.se});
}else {
ans.push_back({x.fi, y.fi, x.se});
}
}
cout << ans.size() << endl;
for(auto [u, v, w] : ans) cout << u << " " << v << " " << w << endl;
}
错误次数
无
文 / WIDA
2022.01.17 成文
首发于WIDA个人博客,仅供学习讨论
更新日记:
2022.01.18 修改标签
2022.01.17 成文