Vijos 奖金 题解
简要题意:
已知每个员工的奖金至少是 \(100\) 元,已知若干组关系 \(x\) 和 \(y\),必须满足 \(x\) 的奖金比 \(y\) 多(至少 \(1\) 元)。求满足所有关系的最少需要发放的奖金。(无解输出 Poor Xed
)
首先,我们考虑,什么情况是无解的?
如果你想不到,那么给出这样 \(3\) 种关系你看看:
1 2
2 3
3 1
\(1\) 比 \(2\) 多,\(2\) 比 \(3\) 多,\(3\) 比 \(1\) 多,这不可能满足。
所以,如果 反向建图(即 \(x\) 到 \(y\) 有边当且仅当 \(x\) 的奖金 \(< y\) 的奖金) 的话,出现环即说明无解。
那么,剩下的情况怎么计算呢?
你会说,显然啊,剩下是 有向无环图,即 \(\text{Dag}\) 图,可以用拓扑排序的方式。
没错,思路是这样的,正解也是这样的,但是有很多细节。
下面给出一些特殊例子,用 \(f_i\) 表示 \(i\) 号节点的奖金。
1 2
1 3
如果你建的是 无向图 的话,从 \(2\) 开始搜,那么 \(f_2=100 , f_1 =101,f_3=102\),然后 关系乱伦 。
所以我们应当建有向图。
如果,你从任意一个入度为 \(0\) 的点开始搜并统计路径上答案,那么反例仍然这组:
1 2
1 3
你在搜 \(2,3\) 的时候把 \(1\) 重复加了。
那你说,我可以用哈希啊。
没错,这就是正解,用 \(\texttt{bfs}\),即 宽度优先搜索 实现本题,具体细节见代码。
时间复杂度:\(O(n+m)\).
实际得分:\(100pts\).
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+1;
inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}
int n,m,du[N],ans;
int sum=0,num=0;
vector<int> G[N];
bool vis[N];
queue<int> q;
int main(){
// freopen("reward.in","r",stdin);
// freopen("reward.out","w",stdout);
n=read(),m=read();
while(m--) {
int x=read(),y=read();
du[x]++; G[y].push_back(x); //记录入度,建图
} goto fin;
fin: { //利用 goto 的特性完成 while 循环,炫一波特技
bool f=0;
for(int i=1;i<=n;i++)
if(!du[i] && !vis[i]) {
q.push(i);
f=1; vis[i]=1;
ans+=100;
} //把入度为 0 且没有访问过的点入队,统计
if(!f) goto last; //直接跳出
while(!q.empty()) {
int x=q.front(); q.pop();
num++; ans+=sum; vis[x]=1;
//num 统计人数,ans 是奖金总数
for(int i=0;i<G[x].size();i++) du[G[x][i]]--; //删边并更新入度
} sum++; //sum 表示当前人的奖金,每走一步增大一个
goto fin; //自己 goto 自己,重新跳回 fin,实现 while
} last: { //goto 就是一波神奇炫技操作
if(num==n) printf("%d\n",ans);
else puts("Poor Xed");
}
return 0;
}
简易的代码胜过复杂的说教。