HUD2647 Reward_反向建图拓扑排序
HDU2647 Reward
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2647
题意:老板要发奖金了,有n个人,给你m对数,类似a b,这样的一对数,意思是a的奖金需要比b多,问满足老板的给出这些关系最少需要多少钱,如果无法满足老板的条件就直接输出-1.
很明显如果三对数,<a,b><b,c><c,a>存在矛盾,ra>rb,rb>rc,rc>ra(r前缀表示奖金),是不可能的,也就是说这个图存在环的情况下,无法满足老板的条件。
有向图判圈,我们想到可以用并查集,但是对于1-n个数两两判圈,面对题目给的数据量肯定是会超时的,通过这道题,我也学习了一个新姿势,接下来介绍一下。
首先这道题后续节点的奖金数决定了前续节点,所以我们需要反向建图,保证一个点的所有的后续节点都知道奖金值得时候才给这个点赋值奖金数。接下来,我们发现对于拓扑排序的我们记录每一个点的出度。当一个点的出度通过剪边消耗为0的时候,我们把压入队列,最后我们会遍历所有的点。试问我们可不可以这样理解拓扑排序中出现环的情况,我们把环缩成一个点,在一个环上的点无法用拓扑排序比较出大小,在拓扑排序的时候的时候我们实际上是把一个环作为一个整体,与其他非环部分拓扑排序。所以我们排序完以后,我们不会遍历所有的点,因为对于一个环,我们遍历了他的一个点。而其余的点我们并未遍历。这样我们可以通过遍历的点数判断是否出现环,还有就是对于一个点在剪边的过程中我们要维护他奖金的最大值。下面是代码……
//Author: xiaowuga #include <bits/stdc++.h> #define maxx INT_MAX #define minn INT_MIN #define inf 0x3f3f3f3f const long long N=100000+10; using namespace std; typedef long long L; vector<int>q; vector<int>p[N]; int in[N]; int main(){ ios::sync_with_stdio(false);cin.tie(0); int n,m; while(cin>>n>>m){ q.clear(); memset(in,0,sizeof(in)); for(int i=1;i<=n;i++) p[i].clear(); q.clear(); int reward[N]; for(int i=1;i<=n;i++) reward[i]=888; for(int i=0;i<m;i++){ int a,b; cin>>a>>b; p[b].push_back(a);//反向建图 in[a]++;//记录出度 } int ct=0,ans=0; for(int i=1;i<=n;i++) if(!in[i]) {q.push_back(i);ans+=reward[i];} while(q.size()!=0){ int t=q.back();q.pop_back(); ct++; for(int i=0;i<p[t].size();i++){ int x=p[t][i]; if(--in[x]==0){ q.push_back(x); reward[x]=max(reward[x],reward[t]+1);//维护一个点奖金的最大值 ans+=reward[x];//出度减少为0的时候,ans+=reward[i] } else{ reward[x]=max(reward[x],reward[t]+1);//维护一个点奖金的最大值 } } } if(ct!=n){//判断是否遍历了所有的点 cout<<-1<<endl; } else cout<<ans<<endl; } return 0; }