【题解】 Nobel 0-1分数规划+负环+最短路+数学

Legend

对于一个 \(n\) 个点 \(m\) 条边的带权有向图,找到一条 \(1 \to n\) 的路径使得标准差尽量小。

\(x_1,x_2,\cdots ,x_k\) 的标准差定义为:

\[S=\sqrt{\frac{1}{k}\sum_{i=1}^{k}(x_i- \overline x)^2} \]

\(1 \le n \le 30\)\(1 \le m \le 100\),边权为正整数且不超过 \(100\)

Editorial

显然答案路径要么就是一条直接去到 \(n\) 的简单路径,要么是选择了一个简单环在上面绕无穷圈最后收敛到的值。

这种含有平均值类型的题目容易让人想到 0-1分数规划。

但方差(标准差的平方)的计算式子很奇怪,平方项让 0-1 分数规划无从下手。

题目中实际上隐含了一个关于平均数的限制条件:

边权为正整数且不超过 \(100\)

一个简单环或者一条简单路径都最多经过 \(O(n)\) 条,我们可以直接枚举路径上的平均数 \(\overline x\)

因为边权很小,平均数最多只有 \(O(n^2w)\) 种取值。

由于我们枚举了平均数,上式就变成了:

\[s^2 = \dfrac{\sum_{i=1}^n (x_i - \overline x)^2 }{n} \]

显然可以 0-1 分数规划:

\[\sum_{i=1}^n (x_i - \overline x)^2 \le n \times mid \\ \sum_{i=1}^n (x_i - \overline x)^2 - mid \le 0 \]

显然改变一下边权之后就只用判断一下是否存在负权最短路或者负环。

mistake?

其实有一个小问题:这样子直接跑 SPFA 跑出来的路径的平均数并不一定是当前枚举的平均数。

但仔细想想会发现:对于方差的式子,一定是 \(\overline x\) 取到的真正的平均值是最优的。把方差看成若干个二次函数相加就可以证明。

所以这么做其实是对的。

还是很妙的思路呀。

如果暴力枚举平均数的分子分母,复杂度就是 \(O(n^3mw (\log n+ \log w))\)

Code

一个细节是:负环必须可以到达 \(n\),也必须被 \(1\) 到达。

卡常数。

有几个好方法:可以先做一遍背包搞出合法的边权和,只用这些边权去枚举平均值。

我是直接对边权从大到小排序以前缀和为枚举上界,也可以通过。

以及可以在二分之前尝试剪枝。这样就可以跑得飞快啦!

#include <bits/stdc++.h>

#define LL long long
#define debug(...) fprintf(stderr ,__VA_ARGS__)
#define __FILE(x)\
	freopen(#x".in" ,"r" ,stdin);\
	freopen(#x".out" ,"w" ,stdout)

const int MX = 30 + 2;
const double eps = 1e-6;

int read(){
	char k = getchar(); int x = 0;
	while(k < '0' || k > '9') k = getchar();
	while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
	return x;
}

struct EDGE{
	int u ,v ,w;
}e[MX * MX];

int head[MX] ,tot ,n ,m;
struct edge{
	int node ,next;
	double w;
}h[MX * MX];
void addedge(int u ,int v ,double w){
	h[++tot] = (edge){v ,head[u] ,w} ,head[u] = tot;
}

int inq[MX] ,vis[MX] ,q[MX * MX] ,l ,r ,cango[MX];
double dis[MX];
bool check(double mid ,double aver){
	memset(head ,0 ,sizeof head) ,tot = 0;
	for(int i = 1 ; i <= m ; ++i){
		addedge(e[i].u ,e[i].v 
				,(e[i].w - aver) * (e[i].w - aver) - mid);
	}
	for(int i = 1 ; i <= n ; ++i){
		dis[i] = 1 << 30;
		inq[i] = vis[i] = 0;
	}
	l = 0 ,r = 0;
	q[r++] = 1;
	dis[1] = 0;
	while(l != r && dis[n] >= 0){
		int x = q[l++];
		inq[x] = false;
		++vis[x];
		if(vis[x] == n){
			if(cango[x])
				return true;
			else continue;
		}
		for(int i = head[x] ,d ; i ; i = h[i].next){
			if(dis[d = h[i].node] > dis[x] + h[i].w){
				dis[d] = dis[x] + h[i].w;
				if(!inq[d]) inq[d] = 1 ,q[r++] = d;
			}
		}
	}
	return dis[n] < 0;
}

void check(){
	for(int i = 1 ; i <= m ; ++i){
		addedge(e[i].v ,e[i].u ,1);
	}
	l = 0 ,r = 0;
	q[r++] = n;
	cango[n] = 1;
	while(l != r){int x = q[l++];
		for(int i = head[x] ,d ; i ; i = h[i].next){
			if(!cango[d = h[i].node]){
				cango[d] = 1;
				q[r++] = d;
			}
		}
	}
}

int W[MX * MX];

int main(){
	__FILE(nobel);
	n = read() ,m = read();
	for(int i = 1 ,u ,v ,w ; i <= m ; ++i){
		u = read() ,v = read() ,w = read();
		e[i] = (EDGE){u ,v ,w};
		W[i] = w;
	}

	check();
	std::sort(W + 1 ,W + 1 + m);
	std::reverse(W + 1 ,W + 1 + m);
	for(int i = 2 ; i <= m ; ++i) W[i] += W[i - 1];

	double Ans = 1e4;
	for(int i = 1 ; i <= n ; ++i){
		for(int j = 0 ; j <= W[i]; ++j){
			if(check(Ans ,1.0 * j / i) == 0) continue;
			double l = 0 ,r = Ans + eps ,mid;
			while(r - l > eps){
				mid = (l + r) / 2;
				if(check(mid ,1.0 * j / i)){
					r = mid;
				}
				else{
					l = mid;
				}
			}
			// printf("%d/%d %.7lf\n" ,j ,i ,l);
			Ans = std::min(Ans ,l);
		}
	}
	printf("%.7lf\n" ,sqrt(Ans));
	return 0;
}
posted @ 2020-10-19 16:07  Imakf  阅读(158)  评论(0编辑  收藏  举报