NOIP2009 最优贸易

目前高赞题解使用的是dp解法,但是个人感觉写的不是很清楚。因此尝试做一个更为明了的解释。


题意

给你一个连通图,有单向边也有双向边。对于每一条从(1)(n)的路径,都会有一个途径的最大权值(C_{max})和一个最小权值(C_{min})。问:对于所有((C_{max},C_{min})),最大的(C_{max}-C_{min})

(同一个节点可以多次经过)

思路

朴素的思路是在图上用dfs进行dp转移,对于每个节点记录从1到该节点所有路径的最大差值,然后最后输出n即可。

容易发现,当某个不是n的节点有且只有一条双向边时,它的值无法贡献到答案当中。因此我们需要考虑改进思路。

可以进一步想到,仍然使用dfs,但是根据数据是否被更新来决定是否扩展当前节点,也就是说,每次搜到一个节点的时候都检查一下当前节点会不会对答案做出贡献,或是当前节点的答案需不需要更新,然后再决定是否扩展。

这个思路需要我们定义一个数组(mina[N]),表示上一次我们到达某个节点时的最小权值。

如果目前的最小权值小于这个值,那我们就需要重新扩展这个节点。

同时,如果当前节点的答案小于当前节点值减去最小权值,我们也需要重新扩展。

状态转移方程是显然的。


代码

#include <bits/stdc++.h>

using namespace std;

namespace StandardIO {

	template<typename T>inline void read (T &x) {
		x=0;T f=1;char c=getchar();
		for (; c<'0'||c>'9'; c=getchar()) if (c=='-') f=-1;
		for (; c>='0'&&c<='9'; c=getchar()) x=x*10+c-'0';
		x*=f;
	}

	template<typename T>inline void write (T x) {
		if (x<0) putchar('-'),x*=-1;
		if (x>=10) write(x/10);
		putchar(x%10+'0');
	}

}

using namespace StandardIO;

namespace Project {
	
	const int N=500500;
	const int INF=2147483647;
	
	int n,m;
	int a[N];
	int cnt;
	int head[N];
	struct node {
		int to,next;
	} edge[N<<1];
	int f[N],mina[N];
	
	inline void add (int a,int b) {
		edge[++cnt].to=b,edge[cnt].next=head[a],head[a]=cnt;
	}
	void dfs (int now,int fa,int minn) {
		int flag=1;
		minn=min(minn,a[now]);
		if (mina[now]>minn) mina[now]=minn,flag=0;
		if (f[now]<max(f[fa],a[now]-minn)) f[now]=max(f[fa],a[now]-minn),flag=0;
		if (flag) return;
		for (register int i=head[now]; i; i=edge[i].next) {
			int to=edge[i].to;
			dfs(to,now,minn);
		}
	}

	inline void MAIN () {
		read(n),read(m);
		for (register int i=1; i<=n; ++i) read(a[i]),mina[i]=INF;
		for (register int i=1; i<=m; ++i) {
			int x,y,z;
			read(x),read(y),read(z);
			add(x,y);
			if (z==2) add(y,x);
		}
		dfs(1,0,INF);
		write(f[n]);
	}
	
}

int main () {
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	Project::MAIN();
}

原文地址:https://www.cnblogs.com/ilverene/p/11195780.html