CF1679 D Toss a Coin to Your Graph...

 原题链接:Problem - 1679D - Codeforces

题意翻译

珂朵莉给了你一个有向图,点数为 n ,边数为 m ,每个点有一个点权 a_{i} ,任选起点,走 k-1 步(即包括起点和终点总共 k 个点),问经过的所有点的最大权值的最小是多少?若无解输出 -1。

输入格式

第一行包含三个整数 n,m,k —— 表示图中顶点数,边的数量,以及珂朵莉应该执行的操作的数量。(1 \leq n \leq 2*10^{5}1 \leq m \leq 2*10^{5}1 \leq k \leq 10^{18}1 \leq a_{i} \leq 10^{9}

第二行包含 n 个整数 a_{i} ——表示点的点权。

接下来的 m 行中的每一行都包含两个整数 u 和 v —— 意味着图中有一条边 u→v 。

有向图中没有重边和自环,但是可能会有环。

输出样例

思路

错误:一开始想暴力dfs,求出每一段长度 (1~k) 的最大值的最小值,就是vis[x]++ vis[x]-- ,后来发现不行。

正确:最大值的最小值,自然的想到 二分 ,根据题意来看,我们可以二分答案

要判断一个答案 mid 可不可行,我们先枚举每个点,只留下点权小于等于 mid 的点(vis[i]=1),再枚举每条边,将我们选出来点集重新连成有向图。

现在我们只需要判断新建的图合不合法。

如果这张图有环,那答案显然成立。

若没有环,那我们就要求出这张图的最长路判断它是不是大于等于 k 。

找环,求有向无环图的最长路,这两个可以用拓扑排序同时做。

代码

#include <bits/stdc++.h>
using namespace std;
//#define int long long
#define ll long long
#define fir first
#define sec second
#define rep(i,a,n) for(int i=a;i<n;i++)
#define endl "\n"
template <class...Args> void _(Args... args) {
	auto _ = [&](auto x) {cout << x << " ";};
	cout << "-->";
	int arr[] = {(_(args), 0)...};
	cout << endl;
}
const int N = 1e6 + 7;
const int mod = 1e9+7;

int n, m, mx, a[N], u[N], v[N], du[N], f[N], vis[N];
ll k;
vector<int> chu[N];

int check(int maxx){
	for(int i=1;i<=n;i++){
		chu[i].clear(); vis[i]=0; du[i]=0; f[i]=0;
	} mx=0;
	for(int i=1;i<=n;i++){
		if(a[i]<=maxx) vis[i]=1;
	}
	for(int i=1;i<=m;i++){
		if(vis[u[i]]&&vis[v[i]]){
			chu[u[i]].push_back(v[i]); 
			du[v[i]]++;
		}
	}
	queue<int> q;
	for(int i=1;i<=n;i++){
		if(du[i]==0&&vis[i]) q.push(i);
	}
	if(!q.empty()) mx=1; //_(q.size(),"*");
	while(!q.empty()){
		int t=q.front(); q.pop(); 
		f[t]=1; mx=max(mx,vis[t]);
		for(auto it:chu[t]){
			du[it]--; vis[it]=max(vis[it],vis[t]+1);
			if(du[it]==0) q.push(it);
		}
	}
	//判断是否有环
	int flag=0;
	for(int i=1;i<=n;i++){
		if(vis[i]&&!f[i]){
			flag=1; break;
		}
	}
	
	if(flag==0&&(ll)mx<k) return 0;
	else return 1;
	
}

void solve() {
	cin>>n>>m>>k;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=m;i++) cin>>u[i]>>v[i];
	
	int st=1, ed=1e9, mid, ans=-1;
	while(st<=ed){
		mid=(st+ed)/2; 
		if(check(mid)){
			ed=mid-1; ans=mid;
		}else {
			st=mid+1;
		}
	}
	cout<<ans<<"\n";
}

signed main() {
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int Case=1;
	while(Case--) solve();
	
	return 0;
}

易错点

要单独给 k 开long long /(ㄒoㄒ)/~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值