Vijos 奖金 题解

博客园同步

原题链接

简要题意:

你需要给 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
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;
}

你可能感兴趣的:(Vijos,图,拓扑排序)