【2018 NWERC D】Date Pickup 题解

题目大意

  有一幅 n n n 个点 m m m 条边的有向图,边有边权(代表通过所需时间),你在 1 1 1 号点,女朋友在 n n n 号点。
  你可以选择在 1 1 1 号点延迟任意时间之后,选定一条路线开始游走,一旦开始游走就不能停下来。你的女朋友会在时间区间 [ a , b ] [a,b] [a,b] 中的任意一个实数时间点 call 你,你一旦被 call 就要马上过去 n n n 号点,女朋友的等待时间就是她 call 了之后到你到达所用的时间。
  求女朋友的最坏等待时间最小。

   n , m ≤ 1 0 5 ,    0 ≤ a ≤ b ≤ 1 0 12 n,m \le 10^5,\ \ 0 \le a \le b \le 10^{12} n,m105,  0ab1012,边权 ≤ 1 0 6 \le 10^6 106
  保证每个点至少有一条出边,即总是可以无限游走的。

  6s

\\
\\
\\

题解

  官方题解有很多细节不是很懂,不过对我考场上的想法有很大的启发,于是完成了我考场上的想法。

  二分答案 m i d mid mid
  首先,如果到了 [ a , b ] [a,b] [a,b] 这个时间段,我们必须随叫随到,因此我们可以知道这时候哪些点哪些边是能走的:对于点 u u u 需满足 d i s ( u , n ) ≤ m i d dis(u,n) \le mid dis(u,n)mid,对于边 ( u , v , w ) (u,v,w) (u,v,w) 需满足 w + d i s ( v , n ) ≤ m i d w+dis(v,n) \le mid w+dis(v,n)mid。这个时间段内我们只能在这个子图上走,除了开始的一点点( a a a 时刻我们可能还在去这个子图的路上)。
  如果我们能从 1 1 1 号点去到这个子图,那么接下来要做的事情就是:如果这个子图有环,我们就一直沿着环走,就合法了;如果这个子图没有环,它就是个 DAG,就可以在上面 dp 出一个最久逗留时间,让它 ≥ b \ge b b 就好了。
  所以现在就是要判断从 1 1 1 号点能不能到达这个子图,即会不会在 a a a 时刻还没走到子图但是被 call 了然后去不了 n n n 号点。
  假设在 [ a , b ] [a,b] [a,b] 时间段我们最先到达的节点是 x x x,它的充要条件就是:1、 x x x 是子图上的点;2、 d i s ( 1 , x ) ≤ a + m i d − d i s ( x , n ) dis(1,x) \le a+mid-dis(x,n) dis(1,x)a+middis(x,n)(即 a a a 时刻 call 合法)。这样就相当于筛选出了子图的合法起点,可以做一个 bfs 筛去子图里起点不能到的点,然后做上面的拓扑找环和 dp。

  关于 DAG 上的 dp,初值是对于子图起点 x x x d p x = a + m i d − d i s ( x , n ) dp_x=a+mid-dis(x,n) dpx=a+middis(x,n)(即越晚出发越好,但要满足 a a a 时刻 call 合法),然后按拓扑序求最长路径。

  注意一些特殊情况,比如 1 1 1 号点本身是子图里的点的时候,只需判断 d i s ( 1 , n ) ≤ m i d dis(1,n) \le mid dis(1,n)mid

代码

#include<bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;

typedef long long LL;
typedef pair<int,int> pr;

const int maxn=1e5+5;

int n,m;
LL a,b;
vector<pr> e[maxn],ef[maxn];

LL dis1[maxn],disn[maxn];
bool vis[maxn];
void dijkstra(int st,vector<pr> *e,LL *dis) {
	memset(dis,127,sizeof(LL)*(n+2));
	dis[st]=0;
	memset(vis,0,sizeof(vis));
	priority_queue<pair<LL,int>> Q;
	Q.push(make_pair(0,st));
	fo(i,1,n) {
		while (!Q.empty() && vis[Q.top().second]) Q.pop();
		if (Q.empty()) break;
		auto tp=Q.top(); Q.pop();
		vis[tp.second]=1;
		for(pr go:e[tp.second]) if (!vis[go.first] && dis[go.first]>-tp.first+go.second) {
			dis[go.first]=-tp.first+go.second;
			Q.push(make_pair(-dis[go.first],go.first));
		}
	}
}

bool valid[maxn],arrived[maxn];
vector<pr> et[maxn];
LL dp[maxn];
int dg[maxn];
void bfs(LL mid) {
	memset(dg,0,sizeof(dg));
	memset(arrived,0,sizeof(arrived));
	queue<int> Q;
	fo(i,1,n) {
		et[i].clear();
		if (valid[i] && dis1[i]<=a+mid-disn[i]) {
			Q.push(i);
			arrived[i]=1;
		}
	}
	while (!Q.empty()) {
		int cur=Q.front(); Q.pop();
		for(auto go:e[cur]) if (go.second+disn[go.first]<=mid) {
			if (!arrived[go.first]) Q.push(go.first), arrived[go.first]=1;
			et[cur].push_back(go);
			dg[go.first]++;
		}
	}
}
bool topo(LL mid) {
	memset(dp,0xbf,sizeof(dp));
	queue<int> Q;
	fo(i,1,n) if (arrived[i] && !dg[i]) Q.push(i);
	while (!Q.empty()) {
		int cur=Q.front(); Q.pop();
		if (dis1[cur]<=a+mid-disn[cur]) dp[cur]=max(dp[cur],a+mid-disn[cur]);
		if (dp[cur]>=b) return 1;
		for(auto go:et[cur]) {
			dp[go.first]=max(dp[go.first],dp[cur]+go.second);
			if (--dg[go.first]==0) Q.push(go.first);
		}
	}
	fo(i,1,n) if (arrived[i] && dg[i]) return 1;
	return 0;
}
bool check(LL mid) {
	fo(i,1,n) valid[i]=(disn[i]<=mid);
	if (valid[1]) return 1;
	bfs(mid);
	return topo(mid);
}

int main() {
	scanf("%lld %lld",&a,&b);
	scanf("%d %d",&n,&m);
	fo(i,1,m) {
		int x,y,z;
		scanf("%d %d %d",&x,&y,&z);
		e[x].push_back(make_pair(y,z));
		ef[y].push_back(make_pair(x,z));
	}
	
	dijkstra(1,e,dis1);
	dijkstra(n,ef,disn);
	
	LL l=0, r=dis1[n];
	while (l<=r) {
		LL mid=(l+r)>>1;
		if (check(mid)) r=mid-1; else l=mid+1;
	}
	
	printf("%lld\n",r+1);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
很高兴为您提供 Mathor Cup 2022 D 题的解题思路。 题目描述: 给定一个 $n\times n$ 的矩阵 $A$,其中 $A_{i,j}\in\{0,1\}$。你可以进行任意次以下操作: 1. 将第 $i$ 行取反(即 $A_{i,j}\rightarrow 1-A_{i,j}$); 2. 将第 $j$ 列取反(即 $A_{i,j}\rightarrow 1-A_{i,j}$)。 请你计算通过若干次操作后,能够使得矩阵 $A$ 的每一行和每一列的 $1$ 的个数相等的最小操作次数。 解题思路: 本题可以使用贪心和二分图匹配的思想来解决。具体步骤如下: 1. 统计每一行和每一列的 $1$ 的个数,设 $row_i$ 表示第 $i$ 行的 $1$ 的个数,$col_j$ 表示第 $j$ 列的 $1$ 的个数。 2. 如果每一行和每一列的 $1$ 的个数都相等,那么无需进行任何操作,直接输出 $0$。 3. 如果某一行 $i$ 的 $1$ 的个数多于其他行的 $1$ 的个数,那么可以将该行取反,将 $row_i$ 减一,将 $col_j$ 加一。 4. 如果某一列 $j$ 的 $1$ 的个数多于其他列的 $1$ 的个数,那么可以将该列取反,将 $col_j$ 减一,将 $row_i$ 加一。 5. 重复步骤 3 和步骤 4,直到每一行和每一列的 $1$ 的个数都相等。 6. 计算进行的操作次数,输出结果。 需要注意的是,为了避免重复计算,我们可以使用二分图匹配的思想来进行操作。将每一行和每一列看做二分图的两个部分,如果某一行 $i$ 的 $1$ 的个数多于其他行的 $1$ 的个数,那么可以将第 $i$ 行和所有 $1$ 的个数比该行少的列建立一条边;如果某一列 $j$ 的 $1$ 的个数多于其他列的 $1$ 的个数,那么可以将第 $j$ 列和所有 $1$ 的个数比该列少的行建立一条边。最后,将二分图的最小路径覆盖数乘以 $2$ 就是最小操作次数。 时间复杂度:$O(n^3)$。 完整代码:

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值