题目

城市中有一条长度为 $n$ 的道路,每隔 $1$ 的长度有一个公交车站,车站的编号从 $0$ 到 $n$,市政府在 $0$ 号车站的位置。

城市中同时有 $n$ 趟公交线路,第 $i$ 条公交线路的起点站为 $i-1$。公交车站 $i(0\leq i\leq n)$ 有两个属性 $c_i$ 和 $v_i$,代表从这个公交车站出发的公交车的性质。

$c_i$ 代表这个从 $i$ 出发的公交车,相邻两个停靠站之间的距离。$v_i$ 表示每坐一站的花费。

注意,一辆公交车出发后会向 $n$ 号车站的方向行进。同时,一名乘客只能从起点站上车,但可以从任意停靠站下车。

形式化地:

  • 如果在第 $i$ 个公交车站上车,你可以选择一个正整数 $k(i+k\times c_i\leq n)$,然后在 $i+k\times c_i$ 这个站点下车,这个过程需要花费 $k\times v_i$ 的代价。

你需要分别求出从 $0$ 号车站乘公交车到 $i$ 号车站的最小花费(每个最小花费是独立的询问)。

分析

容易发现转移:

$$f_i=\min_{c_j|i-j}\left\{f_j+\dfrac{i-j}{c_j}\cdot v_j\right\}$$

转移带乘法,考虑斜率优化。但是由于 $c_i$ 的限制,不能直接优化,考虑突破这个限制:对它分类,一类 $\{c_i,i\bmod c_i\}$ 到达的地点都一样,维护 $\text{cmax}^2$ 个类即可。然后,把转移方程看作是直线方程。斜率公式为:

$$\begin{align*}f_j+\dfrac{i\cdot v_j}{c_j}-\dfrac{j\cdot v_j}{c_j}&\leq f_k+\dfrac{i\cdot v_k}{c_k}-\dfrac{k\cdot v_k}{c_k}\f_j-f_k-\dfrac{j\cdot v_j}{c_j}+\dfrac{k\cdot v_k}{c_k}&\leq \dfrac{i\cdot v_k}{c_k}-\dfrac{i\cdot v_j}{c_j}\\dfrac{f_j-f_k-\dfrac{j\cdot v_j}{c_j}+\dfrac{k\cdot v_k}{c_k}}{v_k-v_j}&\leq \dfrac{i}{c} \tag{1}\end{align*}$$

其中 $(1)$ 式中,在同一类 $c_j=c_k$,这里直接化作 $c$。

代码

const int N = 1e6 + 10;

inline ll Read() {
	ll x = 0, f = 1;
	char c = getchar();
	while (c != '-' && (c < '0' || c > '9')) c = getchar();
	if (c == '-') f = -f, c = getchar();
	while (c >= '0' && c <= 	'9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
	return x * f;
}

int n, mxc;
int c[N], v[N];
vector<int> st[11][11];
int f[N];

double slope(int i, int j) {
	if (v[i] == v[j]) return 1010580540;
	return (double)(f[i] - f[j] - i * v[i] / c[i] + j * v[j] / c[j]) / (v[j] - v[i]);
}

int calc(int i, int j) {
	return f[j] + (i - j) / c[j] * v[j];
}

int main() {
	freopen("bus.in", "r", stdin);
	freopen("bus.out", "w", stdout);
	n = Read(), mxc = Read();
	for (int i = 0; i < n; i++) c[i] = Read(), v[i] = Read();
	memset (f, 60, sizeof f);
	f[0] = 0;
	for (int i = 0; i < n; i++) {
		int x = c[i], y = i % c[i];
		while (!st[x][y].empty() && v[i] <= v[st[x][y][st[x][y].size() - 1]]) st[x][y].pop_back();
		while (st[x][y].size() >= 2 && 
			slope(i, st[x][y][st[x][y].size() - 1]) >=
			slope(st[x][y][st[x][y].size() - 2], st[x][y][st[x][y].size() - 1])) st[x][y].pop_back();
		st[x][y].push_back(i);
		for (int j = 1; j <= mxc; j++) {
			x = j, y = (i + 1) % j;
			while (st[x][y].size() >= 2 && calc(i + 1, st[x][y][st[x][y].size() - 1]) >=
				calc(i + 1, st[x][y][st[x][y].size() - 2])) st[x][y].pop_back();
			if (!st[x][y].empty()) f[i + 1] = min(f[i + 1], calc(i + 1, st[x][y][st[x][y].size() - 1]));
		}
	}
	for (int i = 1; i <= n; i++) 
		if (f[i] >= 1010580540) printf ("-1 ");
		else printf ("%d ", f[i]);
	return 0;
}
EOF

评论

暂无评论

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。

博客信息

作者
Jayun
时间
2022-10-17 21:14:51
博客类型