“新智认知”杯上海高校程序设计竞赛暨第十七届上海大学程序设计春季联赛----G-CSL的训练计划

首先发出题目链接:
链接:https://ac.nowcoder.com/acm/contest/551/F
来源:牛客网
涉及:有向图


题目如下:
题目
描述
描述
题目意思(表示我太chun,读了几遍才勉强明白了题目意思):

首先输入三个数n,m,s;
n是有多少个学员(dalao),m是学员之间的强弱关系有几条,s是所有学员即将分的所有题目;

下面每一行代表一种学员之间的强弱关系x,y,z
其中x和y指学员编号且y学员比x牛逼,如果x学员做了a道题,那么y学员至少要做 a + r ∗ k a+r*k a+rk道题(比如输入1 3 0指3号学员比1号学员牛逼,如果1号学员做了0道题,那么3号学员就至少要做 r ∗ k r*k rk道题)

也就是说,在所有的学员关系中,至少有一个最牛逼的学员,也至少有一个最菜的学员,


这样想一想,这不就是一个有向图吗?每条学员关系就是一条有向路,从x走向y的一条路,然后r算是一条路的权值,每条路的路径长为起点的值a+边权r*常数k( a + r ∗ k a+r*k a+rk),每一个点都有一个值。

最菜学员的编号是图的起点,最牛逼学员的编号是图的终点
ps:起点和终点不是唯一的!

而现在让你做的事情就是,找一个最大的k值,使从每一个图的起点到每一个图的终点的路程最大(所以理所当然,图起点的值为0),每一个点的值代表从图的起点到这个点的最短距离中的最大的一个值,所有点的值之和不能超过s。

所以这个题实质是:多源有向图最长路径问题


有向图一般可以用vector来存,为了方便,可以创建一个数对类型的vector(地图)

typedef pair<int,int> P;
vector<P> vec;

其中数对first代表一条有向边的终点,second代表这条边的边权;
vector下标代表这条边的起点(比如vec[1]=P(2,3)指起点是1号点,终点是2的一条边权为3的边)

有了地图,如何找到合适的k值,使得k值最大,且图中所有点的值之和不超过s。

对于k值,肯定不能从0开始一个个来模拟搜索,可以采用二分的方式来判断k可以有多大。
k的大概范围从0到 s / m a x ( r ) s/max(r) s/max(r) m a x ( r ) max(r) max(r)指所有边权中最大的边权
证明:我们取极限情况,图中只有两个点,起点值为0,终点的值就是s,此时k值最大为 s / m a x ( r ) s/max(r) s/max(r),在这个情况下,图中只有一条路,只有一个边权,所以 m a x ( r ) = r max(r)=r max(r)=r。如果继续往图中加点连边,k只会越来越小,所以k的最大范围是 s / m a x ( r ) s/max(r) s/max(r)

ps:特判,当所有边权全为0时,k是无限大的!!


有了k值,然后进行模拟,需要从图的起点开始往图的终点来计算(优先宽度搜索),使每一个点到图的起点的距离最短,取所有最短距离中最大的值作为这个点的值(因为要满足题目中的“至少”)。

为了寻找到到这个点所有最短距离中最大的值,可以先判断终点为此点的所有边,每次选择最大a+k*r的值即可,可以使用队列实现过程

(可以定义一个数组big,big[i]存终点为i的边的个数,每次访问了终点为i的边后,big[i]减一,当big[i]=0时,说明所有终点为i的边全部访问完毕,i这个点的值自然就是最大的a+k*r值,就可以把这个点放入队列内,开始搜索起点为此点的边,来更新更高级的点的值)

然后求所有点的值之和,如果大于s,说明k太大了,如果小于等于s,说明k太小了,然后一步步缩小k的范围,直到范围左边界大于右边界时,左边界的值减一就是答案;


比如实例一
地图:
在这里插入图片描述

0
2
2
1
4
1
3
4
2

k的范围是[first,second],此时first=0,second=4,k=(first+second)/2=2;
得到:

0
2
2
1
4
1:0
3:4
4:12
2:0
sum=16,ok

然后令first=k+1
k的范围是[first,second],此时first=3,second=4,k=(first+second)/2=3;
得到:

0
2
2
1
4
1:0
3:6
4:18
2:0
sum=24,no

然后令second=k-1
k的范围是[first,second],此时first=3,second=3,k=(first+second)/2=3;
得到:

0
2
2
1
4
1:0
3:6
4:18
2:0
sum=24,no

然后令second=k-1
second<first,跳出二分循环,得到k=first-1=2;

(记得一定要当first>second时才能跳出循环,不能first>=second时跳出,因为有可能当k=second时为答案!!!)


代码如下:

#include <vector>
#include <queue>
#include <iostream>
#include <algorithm>
typedef long long ll;
using namespace std;
ll first,second; //二分求k的左右范围
ll big[200005]={0};//b[i]记录终点为i这个点的边数
ll rbig[200005]={0};//big数组的拷贝,用于每次二分使用
ll d[200005]={0};//记录每个点的值
ll k=0,sum=0;//sum求所有点的值之和
ll maxr=0; //求最大的边权
typedef pair<int,int> P;
vector<P> vec[200005];//有向图的地图,记录每一条边,vec下标是边的起点,first是边的终点,second是边权
ll n,m,s,x,y,r;//题目涉及的变量
int main(){
	queue<ll> que;//宽搜每一个点
	scanf("%lld%lld%lld",&n,&m,&s);
	while(m--){//开始绘制地图,big记录终点为某个点的边数
		scanf("%lld%lld%lld",&x,&y,&r);
		maxr=max(maxr,r);
		vec[x].push_back(P(y,r));
		big[y]++;
	}
	if(!maxr){printf("-1");	return 0;}//特判
	first=0;second=s/maxr;		
	while(first<=second){//二分搜索k
		k=(first+second)/2;
		sum=0;
		while(!que.empty()) que.pop();//清空数组
		for(ll i=1;i<=n;i++){
			if(!big[i])	que.push(i);//将图的起点放入队列
			rbig[i]=big[i];//拷贝big数组
			d[i]=0;//清空所有点的值
		}	
		while(!que.empty()){
			ll p=que.front();	que.pop();
			sum+=d[p];//记录此点的值
			if(sum>s)	break;//如果sum>s直接跳出
			for(ll i=0;i<vec[p].size();i++){
				d[vec[p][i].first]=max(d[vec[p][i].first],d[p]+k*vec[p][i].second);//更新点的值
				rbig[vec[p][i].first]--; //未访问的终点为此点的边的数量减一
				if(rbig[vec[p][i].first]==0)	que.push(vec[p][i].first);//所有终点为此点的边全部被访问过,d[i]为最终值,将此点放入队列
			}	
		}
		if(sum>s)	second=k-1;
		else	first=k+1;//改变范围
	}
	printf("%lld",first-1);
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值