用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;
}
posted @ 2020-09-28 20:21  liuchanglc  阅读(153)  评论(0编辑  收藏  举报