前言
- 我,没记错的话,这是传说中的 noi 篇啊,忘的一干二净
- 我去,反省一段时间
题解
- 首先这里的 \(k\) 短路指的是非严格的
- 比较自然的我们有 A* 搜索,维护到达 \(u\) 花费的代价为 \(w\),估计函数为 \(w+dis(u,t)\)
- 其中 \(dis(x,y)\) 表示 \(x\) 到 \(y\) 的最段路。用优先队列辅助搜索即可。
- 我们考虑复杂度瓶颈在那里,其实是在我们每个状态的出路有多条,还有我们无法估计什么时候 \(u\) 才能到达 \(t\)
- 其实就是我们维护的是中间状态,而不是一种结果状态。
- 好好好,那就干脆把中间状态改为结果状态,怎么改,就直接钦定 \(u\) 到 \(t\) 的路直接走最短路。
- 最短路有多条怎么办呢?我就直接钦定一条呗,说啥子呢?其实就是建立最短路树,树根为 \(t\) 沿着树边不断走向父亲,直到 \(t\)。
- 好好好,这是什么,这是多完美的一个结果状态啊,接下来我们借助计数的思想,保持以选的 \(u\) 和之前的边不变,考虑新增一条边作转移。一条边选和不选是有区别的,后面什么也是不同的,所以我们所产生的所有结果状态都是不重的。
- 接着考虑建立最短鲁树,一条路径必然能在最段路树上和一些非树边刻画,而且这种状态一定能被搜到。所以不重,不漏。
- 结果态有了,剩下的就是想办法优化转移。
- 因为我们复杂度的瓶颈在于,状态更新需要枚举下一条边选什么,我们尝试像线段树优化建图一样。
- 先观察,我们下一步能走非树边,和树边向上。
- 对于树边那是简单我们直接向上走就行了,诶等等,等等,我们默认自动补全的是沿着树向上走的,那我们向上走的还记录状态其不是重了!那怎么办呢?这意味着我们下次转移需要忽略一段沿着树边上向的状态,我们需要直接考虑当前点 \(u\),到 \(t\) 这条链上所有的点,能到的非树边。这些边我们需要一起考虑。
- 我们肯定不能一股脑的暴力搜索那些非树边。但我们可以考虑类似闯关游戏的链状结构,找到所有可选非树边的最小的那条,更新状态,状态下次转移可以考虑继续在边集中找次小边。
- 这样说有些含糊,我们直接说用可持久化可并堆维护,首先每个点的堆维护所有的非树边转移。儿子继承父亲就好了。
- 之后我们记录一下可并堆的堆,转移的话分为两种,第一种考虑下一条边,第二种考虑从堆中换一条边。
后言
- 优化一:其实我们可以假删,把左儿子状态,右儿子状态加入优先队列,这样的话就不用有删除后合并的 \(log\) 复杂度了。
- 优化二:其实堆合并的时候,两堆全值相同,取深度小的为根合并,其实更优。在本题中就很容易让堆的结构类似与左偏的链,pop 的话基本不用新建节点, merge 的话有时后也近乎与 \(O(1)\)
- 这两个选一个都可以通过本题。
#include <bits/stdc++.h>
using namespace std;
typedef double db;
const int N = 5050, M = 200200;
struct node { int u; db dis; }; bool operator< (const node& a, const node& b) { return a.dis < b.dis; }
long long sss = 0;
namespace Heap
{
struct heap { heap* ls, * rs;int d;node v; };
typedef heap* pode;
pode merge(pode n, pode y)
{
if (!n)return y; if (!y)return n;
if (y->v < n->v)swap(n, y);
pode x = new heap(*n);sss++;
x->rs = merge(x->rs, y);
if (x->ls)
{
if (x->ls->d < x->rs->d)swap(x->ls, x->rs);
x->d = x->rs->d + 1;
}
else swap(x->ls, x->rs), x->d = 1;
return x;
}
struct Heap
{
pode p; Heap() { p = nullptr; } Heap(const pode& a) { p = a; }
void pop() { p = merge(p->ls, p->rs); };
node top() { return p->v; }
bool cnt() { return p->ls || p->rs; }
bool have() { return p != nullptr; }
void push(const node& v) { p = merge(p, new heap{ nullptr, nullptr, 1, v }); }
};
}
typedef Heap::Heap heap;
struct hdb { heap t; db w; }; bool operator< (const hdb& a, const hdb& b) { return a.w > b.w; }
struct uw { int u; db w; }; bool operator< (const uw& a, const uw& b) { return a.w > b.w; }
struct edge { int ne, to;db w; }e[M << 1];
db dis[N], s; heap rt[N]; int vis[M << 1], cnt[N], z[N], f[N], n, m, ans = 0;
void add(int u, int v, db w)
{
static int tot = 0;
e[tot] = { z[u],v,w };z[u] = tot++;
e[tot] = { f[v],u,w };f[v] = tot++;
}
void dfs(int u)
{
cnt[u] = 1;
for (int i = z[u];i != -1;i = e[i].ne)if (!vis[i])
rt[u].push({ e[i].to,dis[e[i].to] + e[i].w - dis[u] });
for (int i = f[u];i != -1;i = e[i].ne)if (!vis[i])
{
const int v = e[i].to;
if (!cnt[v] && dis[v] == dis[u] + e[i].w)
vis[i] = vis[i ^ 1] = 1, rt[v] = rt[u], dfs(v);
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
cin >> n >> m >> s;
for (int i = 1;i <= n;i++)z[i] = f[i] = -1, dis[i] = 1e18;
for (int i = 1;i <= m;i++)
{
int u, v;db w;
cin >> u >> v >> w;
if (u == n)continue;
add(u, v, w);
}
priority_queue<uw> q; priority_queue<hdb> p;
dis[n] = 0;q.push({ n,0 });
while (q.size())
{
uw x = q.top();q.pop();
if (dis[x.u] != x.w)continue;
for (int i = f[x.u];i != -1;i = e[i].ne)
{
uw y = { e[i].to,x.w + e[i].w };
if (dis[y.u] > y.w)dis[y.u] = y.w, q.push(y);
}
}
dfs(n);
rt[0].push({ 1,0 }); p.push({ rt[0],0 });
while (p.size())
{
hdb u = p.top();p.pop();//cout << s << " " << sss << '\n';
if (u.w + dis[1] <= s)s -= u.w + dis[1], ans++;else break;
const int to = u.t.top().u;
if (to != n && rt[to].have())p.push({ rt[to],u.w + rt[to].top().dis });
u.w -= u.t.top().dis;
if (u.t.p->ls)p.push({ heap(u.t.p->ls),u.w + u.t.p->ls->v.dis });
if (u.t.p->rs)p.push({ heap(u.t.p->rs),u.w + u.t.p->rs->v.dis });
}
cout << ans << '\n';
return 0;
}