JZOJ5415. 公交运输

题目大意

在数轴上从0nn+1个点, 其中0n1n个位置分别有公交车, 只能向数轴正方向移动, 每个点的公交车有两个属性civi, 分别表示隔几个点停一次(一站要经过几个点)坐一站的花费, 求从0到每个点的最小花费, 无法到达者输出1.
n<=106,maxc<=10.

解题思路

简单思考得到一个dp:设fii点的最小花费, 转移显然.
思考如何优化转移:设当前已经求完答案的点为i, 那么这个点对后面的点的影响可以看做一个一次函数(直线), 斜率为vici, 且有一个点(i,f[i]), 凭此即可确定函数解析式.
然而有一个问题:不是每一个点都能用这条直线来更新答案的.
怎样的点能被这条直线更新呢? 也就是说这条直线只有在公交车停的站才有贡献嘛! 所以满足条件的点的集合是

{j|j>ici|(ji)}

{j|j>iji(mod ci)}

也就是只要jmodci=imodci 即可被转移.
看数据范围:maxc<=10! 由于一共最多只有10c, 且imodci的取值最多不超过ci种, 于是总共最多只有110i种情况, 即55种情况.

所以... 按照55种情况分类的话...
我们只用开55棵李超树就好了!!!

开这么多棵树空间复杂度怎么办呢? 容易想到动态开点. 由于一共只有n条直线, 所以只会有n个树节点.
对于每一个j, 在李超树上枚举每一种c, 设枚举的值为p, 查询(p,jmodp)对应的李超树. 求完答案后丢进李超树即可.
由于李超树查询的复杂度是O(logn), 插入的复杂度是O(log2n), 一个点要做maxc次查询和1次插入, 所以总时间复杂度为O(n(maxclogn+log2n))
虽然n<=106, 但是由于 数据可能是随机的 做人要有信仰 节点数比较少根本跑不满,并且时限2s, 所以简 单 的常数优化以后就能过了.

后记

这种题, 思路很自然, 但是时间复杂度比较清奇, 所以考场没敢写. 实际上就算被卡了也会有80pts, 所以以后要大胆写算法, 大胆开数据结构, 有时间会学习优秀的单调栈做法咕咕咕咕...

#include <cstdio>
#include <cstring>
#include <iostream>
#define N 1000010
#define db double
#define ll long long
#pragma GCC optimize(2)
#define INF 0x3f3f3f3f3f3f3f3fll
#define init(a, b) memset(a, b, sizeof(a))
#define fo(i, a, b) for(int i = (a); i <= (b); ++i)
#define fd(i, a, b) for(int i = (a); i >= (b); --i)
using namespace std;
inline int read()
{
	int x = 0; char ch = getchar();
	while(ch < '0' || ch > '9')	ch = getchar();
	while(ch >= '0' && ch <= '9')	x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
	return x;
}
int n, c[N], v[N];
ll f[N];
struct Line
{
	db k, b;
	Line(){k = 0, b = INF;}
	Line(db _k, db x, db y){k = _k; b = y - k * x;}
	inline db y(db x){return k * x + b;}
};
namespace Seg
{
	int cnt, to[11][11], rt[60], ls[N], rs[N];
	Line tr[N];
	inline void build(int k, int i){static int tot = 0; !rt[to[k][i]] && (rt[to[k][i] = ++tot] = ++cnt);}
	void insert(int t, int l, int r, Line p)
	{
		int mid = (l + r) >> 1;
		db lx = tr[t].y(l) - p.y(l), mx = tr[t].y(mid) - p.y(mid), rx = tr[t].y(r) - p.y(r);
		if(lx >= 0 && rx >= 0)	tr[t] = p;
		else if(lx < 0 && rx >= 0)
		{
			if(mx >= 0)	!ls[t] && (ls[t] = ++cnt), insert(ls[t], l, mid, tr[t]), tr[t] = p;
			else !rs[t] && (rs[t] = ++cnt), insert(rs[t], mid + 1, r, p);
		}
		else if(lx >= 0 && rx < 0)
		{
			if(mx >= 0)	!rs[t] && (rs[t] = ++cnt), insert(rs[t], mid + 1, r, tr[t]), tr[t] = p;
			else !ls[t] && (ls[t] = ++cnt), insert(ls[t], l, mid, p);
		}
	}
	inline void insert(int k, int i, Line p){build(k, i); insert(rt[to[k][i]], 1, n, p);}
	db query(int t, int l, int r, int x)
	{
		db ret = tr[t].y(x);
		int mid = (l + r) >> 1;
		if(x <= mid){if(ls[t])	ret = min(ret, query(ls[t], l, mid, x));}
		else if(rs[t])	ret = min(ret, query(rs[t], mid + 1, r, x));
		return ret;	
	}
	inline db query(int k, int i, int x){return rt[to[k][i]] ? query(rt[to[k][i]], 1, n, x) : INF;}
}
int main()
{
	freopen("bus.in", "r", stdin);
	freopen("bus.out", "w", stdout);
	n = read(), c[0] = read();
	fo(i, 1, n)	c[i - 1] = read(), v[i - 1] = read();
	Seg::insert(c[0], 0, Line(1.0 * v[0] / c[0], 0.0, 0.0));
	fo(i, 1, n)
	{
		f[i] = INF;
		fo(j, 1, 10)
			f[i] = min(f[i], (ll)(Seg::query(j, i % j, i) + 0.5));
		printf("%lld ", f[i] == INF ? -1 : f[i]);
		f[i] != INF && i < n && (Seg::insert(c[i], i % c[i], Line(1.0 * v[i] / c[i], i, f[i])), 1);
	}
	return 0;
}
posted @   Martin_MHT  阅读(88)  评论(1编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示