用Dij的思想优化DP
一、内容
如果 \(DP\) 的状态转移方程为 \(f[i]=min\{f[i],\sum f[j]+k\}\)
那么我们就可以考虑用 \(Dij\) 的思想去优化它
因为如果某个点的 \(f\) 值是最小的,那么就没有其它的点可以影响它
因此我们每一次从堆中取出最小的点对其它点进行更新即可
二、例题
1、洛谷P4745 [CERC2017]Gambling Guide
题目描述
分析
按照期望题一般的做法,我们设 \(f[u]\) 为从 \(u\) 走到终点 \(n\) 的期望花费
设 \(du[u]\) 为节点 \(u\) 的出度
那么 \(f[u]= \frac{\sum_{u->v} min(f[u],f[v])}{du[u]}+1\)
我们会发现这个式子既包含 \(f[u]\) 本身又包含与 \(f[u]\) 相邻的点 \(f[v]\)
不好直接转移
但是我们可以确定,如果 \(f[v]<f[u]\) ,那么 \(f[v]\) 一定会对 \(f[u]\) 做出贡献
因此,我们可以利用 \(Dij\) 的思想开一个小根堆,每次取出最小的来更新其它的
因为当前的值是最小的,所以一定不会有其它的点可以影响它
我们设 \(u\) 被与它相邻的点的 \(f\) 值更新了 \(cnt\) 次,这些值的和为 \(sum\)
则 \(f[u]=\frac{sum+(du[u]-cnt[u]) \times f[u]}{du[u]}+1\)
整理可得 \(f[u]=\frac{sum+du[u]}{cnt}\)
代码
#include<cstdio>
#include<cstring>
#include<queue>
const int maxn=6e5+5;
inline int read(){
int x=0,fh=1;
char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') fh=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*fh;
}
int head[maxn],tot=1;
struct asd{
int to,next;
}b[maxn];
void ad(int aa,int bb){
b[tot].to=bb;
b[tot].next=head[aa];
head[aa]=tot++;
}
struct jie{
int num;
double jl;
jie(){}
jie(int aa,double bb){
num=aa,jl=bb;
}
bool operator < (const jie &A) const{
return jl>A.jl;
}
};
int n,m,du[maxn],cnt[maxn];
double sum[maxn],f[maxn];
bool vis[maxn];
std::priority_queue<jie> q;
void dij(){
q.push(jie(n,0));
while(!q.empty()){
int now=q.top().num;
q.pop();
if(vis[now]) continue;
vis[now]=1;
for(int i=head[now];i!=-1;i=b[i].next){
int u=b[i].to;
if(vis[u]) continue;
cnt[u]++;
sum[u]+=f[now];
f[u]=(du[u]+sum[u])/cnt[u];
q.push(jie(u,f[u]));
}
}
}
int main(){
memset(head,-1,sizeof(head));
n=read(),m=read();
for(int i=1;i<=m;i++){
int aa,bb;
aa=read(),bb=read();
ad(aa,bb);
ad(bb,aa);
du[aa]++,du[bb]++;
}
dij();
printf("%.10f\n",f[1]);
return 0;
}
2、李青(内部题)
题目描述
分析
我们设消灭第 \(i\) 个头的代价是 \(f[i]\)
则,\(f[i]=min(f[i],a[i]+\sum f[j],b[i])\)
同样地,我们在一开始把所有的 \(f\) 值扔进小根堆里
每次取出队首的元素来更新其它值
代码
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<iostream>
#include<cstdlib>
const int maxn=6e5+5;
typedef long long ll;
int head[maxn],tot=1;
struct asd{
int to,next;
}b[maxn];
int n;
void ad(int aa,int bb){
b[tot].to=bb;
b[tot].next=head[aa];
head[aa]=tot++;
}
struct jie{
int num;
ll jl;
jie(){}
jie(int aa,ll bb){
num=aa,jl=bb;
}
bool operator < (const jie &A) const{
return jl>A.jl;
}
};
ll a[maxn],f[maxn];
int k[maxn];
bool vis[maxn];
std::priority_queue<jie> q;
void dij(){
while(!q.empty()){
int now=q.top().num;
q.pop();
if(vis[now]) continue;
vis[now]=1;
if(k[now]==0) f[now]=std::min(f[now],a[now]);
for(int i=head[now];i!=-1;i=b[i].next){
int u=b[i].to;
if(!vis[u]){
a[u]+=f[now];
k[u]--;
if(k[u]==0) q.push(jie(u,a[u]));
}
}
}
}
int main(){
memset(head,-1,sizeof(head));
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lld%lld%d",&a[i],&f[i],&k[i]);
q.push(jie(i,f[i]));
for(int j=1;j<=k[i];j++){
int aa;
scanf("%d",&aa);
ad(aa,i);
}
}
dij();
long long ans=0;
for(int i=1;i<=n;i++){
ans+=f[i];
}
printf("%lld\n",ans);
return 0;
}