简要题意:
你需要给 n n n 个点分配权值,最少是 100 100 100。已知若干组关系 x x x 和 y y y,则你必须满足 x x x 的权值比 y y y 大(至少 1 1 1). 求满足所有关系的最小权值和。
首先,我们考虑,什么情况是无解的?
如果你想不到,那么给出这样 3 3 3 种关系你看看:
1 2
2 3
3 1
1 1 1 比 2 2 2 多, 2 2 2 比 3 3 3 多, 3 3 3 比 1 1 1 多,这不可能满足。
所以,如果 反向建图(即 x x x 到 y y y 有边当且仅当 x x x 的奖金 < y < y <y 的奖金) 的话,出现环即说明无解。
那么,剩下的情况怎么计算呢?
你会说,显然啊,剩下是 有向无环图,即 Dag \text{Dag} Dag 图,可以用拓扑排序的方式。
没错,思路是这样的,正解也是这样的,但是有很多细节。
下面给出一些特殊例子,用 f i f_i fi 表示 i i i 号节点的奖金。
1 2
1 3
如果你建的是 无向图 的话,从 2 2 2 开始搜,那么 f 2 = 100 , f 1 = 101 , f 3 = 102 f_2=100 , f_1 =101,f_3=102 f2=100,f1=101,f3=102,然后 肯定失败 。
所以我们应当建有向图。
如果,你从任意一个入度为 0 0 0 的点开始搜并统计路径上答案,那么反例仍然这组:
1 2
1 3
你在搜 2 , 3 2,3 2,3 的时候把 1 1 1 重复加了。
那你说,我可以用哈希啊。
没错,这就是正解,用 bfs \texttt{bfs} bfs,即 宽度优先搜索 实现本题,具体细节见代码。
时间复杂度: O ( n + m ) O(n+m) O(n+m).
实际得分: 100 p t s 100pts 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;
}