[EC Final 2020] City Brain

7 篇文章 0 订阅
6 篇文章 0 订阅

[Problem   Discription] \color{blue}{\texttt{[Problem Discription]}} [Problem Discription]

有一个由 n n n 个点和 m m m 条边组成的无向图,每条边的长度都是 1 1 1

初始时,每条边限速为 1 1 1。你有 k k k 次操作,每次操作可以使某一条边的限速加 1 1 1 k k k 次操作后,如果一条边的最终限速为 a a a,那么你需要 1 a \dfrac{1}{a} a1 的时间通过这条边。

k k k 次操作后从 s 1 s_{1} s1 t 1 t_{1} t1 和从 s 2 s_{2} s2 t 2 t_{2} t2总时间最小。求出这个最小总时间。

1 ≤ n , m ≤ 5000 , 1 ≤ k ≤ 1 × 1 0 9 1 \leq n,m \leq 5000,1 \leq k \leq 1 \times 10^{9} 1n,m5000,1k1×109

[Analysis] \color{blue}{\texttt{[Analysis]}} [Analysis]

首先,我们要发现这样一个性质:从 s 1 s_{1} s1 t 1 t_{1} t1 的路径(记为 s 1 → t 1 s_{1} \to t_{1} s1t1)和从 s 2 s_{2} s2 t 2 t_{2} t2 的路径(记为 s 2 → t 2 s_{2} \to t_{2} s2t2)如果有公共部分,则公共部分一定连续

证明使用反证法:如果公共部分不连续,则从第一段公共部分的最后一个点到第二段公共部分的第一个点在两条路径中选择了不同的方案。

  • 如果两个方案的耗时不相同,我们可以让耗时较多的那一次也走耗时较少的路径,总时间一定会更小,矛盾。
  • 如果两个方案的耗时相同,为什么非要走不同的路径呢?况且我们可以把对两条路径的操作合并起来对同一段路径操作,这样总时间一定会更小。

可以枚举公共部分的起点和终点,分别记为 u , v u,v u,v。我们发现公共部分的每条边的地位是相等的,非公共部分的每条边的地位也是相等的。即:具体要走怎样的路径对结果没有影响,有影响的是公共部分的总边数 share \text{share} share 和非公共部分的总边数 individual \text{individual} individual

假设对公共部分进行 mid \text{mid} mid 次操作,可以证明这 mid \text{mid} mid 次操作应该尽量平均分在 share \text{share} share 条边上,那么单次行程在公共部分的耗时应该是:

∑ i = 1 mid m o d    share 1 ⌊ mid share ⌋ + 2 + ∑ i = 1 share − ( mid m o d    share ) 1 ⌊ mid share ⌋ + 1 \sum\limits_{i=1}^{\text{mid} \mod \text{share}} \dfrac{1}{ \left \lfloor \frac{\text{mid}}{\text{share}} \right \rfloor +2}+\sum\limits_{i=1}^{\text{share}-(\text{mid} \mod \text{share})} \dfrac{1}{\lfloor \frac{\text{mid}}{\text{share}} \rfloor +1} i=1midmodsharesharemid+21+i=1share(midmodshare)sharemid+11

mid \text{mid} mid 可以用三分求出。

时间复杂度 O ( n 2 + n log ⁡ k ) O \left (n^{2}+n \log k \right ) O(n2+nlogk)

Code \color{blue}{\text{Code}} Code

int n,m,k;

int dis[N][N];
queue<int> q;
inline void bfs(int s){
	for(int i=1;i<=n;i++)
		dis[s][i]=(i==s?0:inf);
	q.push(s);
	
	while (!q.empty()){
		int u=q.front();q.pop();
		
		for(int i=h[u];i;i=e[i].nxt){
			int v=e[i].to;
			if (dis[s][v]==inf){
				dis[s][v]=dis[s][u]+1;
				q.push(v);
			}
		}
	}
}//边权相同,可以用 bfs 求最短路

double calc(int tot,int val){
	if (tot==0) return 0;
	if (val==0) return 1.0*tot;
	
	int ave=val/tot,lef=val%tot;
	return (1.0*(tot-lef)/(ave+1)+1.0*lef/(ave+2));
}//对 tot 条路可以操作 val 次最小时间 

double check(int sha,int idv,int mid){
	return 2.0*calc(sha,mid)+calc(idv,k-mid);
}//别忘了乘以 2

double solve(int sha,int idv){
	int l=0,r=k;
	double ret=1e30;
	
	while (l<=r){
		int lmid=l+(r-l)/3,rmid=r-(r-l)/3;
		
		if (check(sha,idv,lmid)<check(sha,idv,rmid)){
			r=rmid-1;
			ckmin(ret,check(sha,idv,lmid));
		}
		else{
			l=lmid+1;
			ckmin(ret,check(sha,idv,rmid));
		}
	}
	
	return ret;
}

int path[N],s1,t1,s2,t2;
double ans;

int main(){
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1,u,v;i<=m;i++){
		scanf("%d%d",&u,&v);
		add(u,v);add(v,u);
	}
	scanf("%d%d%d%d",&s1,&t1,&s2,&t2);
	
	for(int i=1;i<=n;i++) bfs(i);
	
	for(int i=0;i<=n;i++) path[i]=inf;
	for(int u=1;u<=n;u++)
		for(int v=1;v<=n;v++)
			if (dis[u][v]!=inf){
				if (dis[s1][u]==inf||dis[s2][u]==inf) continue;
				if (dis[v][t1]==inf||dis[v][t2]==inf) continue;
				ckmin(path[dis[u][v]],dis[s1][u]+dis[s2][u]+dis[v][t1]+dis[v][t2]);
				
				if (dis[t2][u]==inf||dis[v][s2]==inf) continue;
				ckmin(path[dis[u][v]],dis[s1][u]+dis[t2][u]+dis[v][t1]+dis[v][s2]);
			}
	
	ans=check(0,dis[s1][t1]+dis[s2][t2],0);
	for(int i=0;i<=n;i++)
		if (path[i]!=inf)
			ckmin(ans,solve(i,path[i]));
	
	printf("%.20lf",ans);
	
	return 0;
}

h,e 是链式前向星使用的数组
ckmin(a,b) 表示让 a 取得 a,b 中的较小值
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值