P4042 [AHOI2014/JSOI2014] 骑士游戏 - 图论,堆
题解
都 2021 年了,不会还真有人用 SPFA 做最短路题吧?
设 \(f_i\) 为消灭 \(i\) 需要的最小花费,则 \(f_i=\min(s_i,k_i+\sum_{(i,j)\in G} f_j)\)。这个转移有环,但考虑一个性质:\(s_i\) 最小的点一定不会被其他点更新。于是我们可以用这个点更新其它点的答案,然后从其它点中取出 \(f\) 最小的一个,进行相同的操作。
这其实就是类似 Dijkstra 的过程,时间复杂度 \(\mathcal{O}((n+\sum r_i)\log n)\)。
还有一种 \(\mathcal{O}(n\log n+\sum r_i)\) 的做法,就是开始时将所有点插入堆,对于每个没被取出的点,直到它被所有连向它的点都松弛了一遍的时候才再次将它入堆。所以每个点最多入堆两次。
代码(法一)
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <ext/pb_ds/priority_queue.hpp>
using namespace std;
#define For(Ti,Ta,Tb) for(int Ti=(Ta);Ti<=(Tb);++Ti)
#define Dec(Ti,Ta,Tb) for(int Ti=(Ta);Ti>=(Tb);--Ti)
template<typename T>
void Read(T &_x){
_x=0;int _f=1;
char ch=getchar();
while(!isdigit(ch)) _f=(ch=='-'?-1:_f),ch=getchar();
while(isdigit(ch)) _x=_x*10+(ch^48),ch=getchar();
_x*=_f;
}
template<typename T,typename... Args>
void Read(T &_x,Args& ...others){
Read(_x);Read(others...);
}
typedef long long ll;
typedef pair<ll,int> pli;
typedef __gnu_pbds::priority_queue<pli,greater<pli>,__gnu_pbds::pairing_heap_tag> Heap;
const int N=2e5+5;
int n;ll s[N],k[N],f[N];
vector<int> g[N],revg[N];
Heap::point_iterator it[N];
int main(){
Read(n);
Heap q;
For(i,1,n){
int len;
Read(s[i],k[i],len);
For(j,1,len){
int x;Read(x);
g[x].push_back(i),revg[i].push_back(x);
}
}
For(i,1,n){
f[i]=s[i];
for(int u:revg[i]) f[i]+=k[u];
it[i]=q.push({min(k[i],f[i]),i});
}
while(!q.empty()){
int u;ll w;
tie(w,u)=q.top();
q.pop();
for(int v:g[u]){
f[v]-=k[u]-w;
if(k[u]>w&&f[v]<k[v]) q.modify(it[v],{f[v],v});
}
}
printf("%lld\n",f[1]);
return 0;
}
Written by Alan_Zhao