【XSY2849】陈姚班 平面图网络流 最短路 DP

题目描述

  有一个\(n\)\(m\)列的网格图。

  \(S\)到第一行的每一个点都有一条单向边,容量为\(\infty\)

  最后一行的每个点到\(T\)都有一条单向边,容量为\(\infty\)

  同一行中相邻的两个节点之间有一条无向边,\((x,y)\)\((x,y+1)\)之间的无向边的容量为\(a_{x,y}\)

  同一列中相邻的两个节点之间有两条有向边,\((x,y)\)\((x+1,y)\)这条有向边的容量为\(b_{x,y}\)\((x+1,y)\)\((x,y)\)这条有向边容量为\(\infty\)

  求\(S\)\(T\)的最大流。

  特别的,\(\forall i,a_{1,i}=a_{n,i}=0\)

  \(n\times m\leq 25000000\)

题解

  显然这是一个网络流。

  直接跑网络流会TLE。

  观察到这个图是一个平面图,可以把平面图网络流转化为对偶图最短路。

  怎么转化呢?

  首先你要会无向边的平面图网络流(可以百度/google)。

  有向边的连边方法和无向边的类似。

  对于一条有向边\(x\rightarrow y\),容量为\(z\)的有向边(网络流最后都是在有向边上面跑的),从\(x\rightarrow y\)这条有向边的左边对应的这个区域连一条边到右边的这个区域,权值为\(z\)

  最后跑一次最短路就行了。

  这道题中从下往上的边的容量为\(\infty\),所以对偶图中从左往右的边的权值为\(\infty\),也就是说最短路的每一步只会向上/下/左走,这就可以DP了。

  设\(f_{i,j}\)为走到\((x,y)\)右下方那个区域的最短路

\[f_{i,j}=\min(f_{i-1,j}+a_{i,j},f_{i+1,j}+a_{i+1,j},f_{i,j+1}+b_{i+1,j}) \]

  时间复杂度:\(O(nm)\)

  我的代码中把左右反过来了

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<ctime>
#include<utility>
#include<cmath>
#include<functional>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
void sort(int &a,int &b)
{
	if(a>b)
		swap(a,b);
}
void open(const char *s)
{
#ifndef ONLINE_JUDGE
	char str[100];
	sprintf(str,"%s.in",s);
	freopen(str,"r",stdin);
	sprintf(str,"%s.out",s);
	freopen(str,"w",stdout);
#endif
}
int rd()
{
	int s=0,c;
	while((c=getchar())<'0'||c>'9');
	do
	{
		s=s*10+c-'0';
	}
	while((c=getchar())>='0'&&c<='9');
	return s;
}
void put(int x)
{
	if(!x)
	{
		putchar('0');
		return;
	}
	static int c[20];
	int t=0;
	while(x)
	{
		c[++t]=x%10;
		x/=10;
	}
	while(t)
		putchar(c[t--]+'0');
}
int upmin(int &a,int b)
{
	if(b<a)
	{
		a=b;
		return 1;
	}
	return 0;
}
int upmax(int &a,int b)
{
	if(b>a)
	{
		a=b;
		return 1;
	}
	return 0;
}
int *a;
int n,m;
int A,B,Q;
int down(int x,int y)
{
	return a[(x-1)*m+y];
}
int right(int x,int y)
{
	return a[(n-1)*m+(x-2)*(m-1)+y];
}
ll *f;
int main()
{
	open("c");
	scanf("%d%d",&n,&m);
	a=new int[(n-1)*m+(n-2)*(m-1)+1];
	scanf("%d%d%d%d",&A,&B,&Q,&a[0]);
	for(int i=1;i<=(n-1)*m+(n-2)*(m-1);i++)
		a[i]=((ll)a[i-1]*A+B)%Q;
	f=new ll[n];
	for(int i=1;i<n;i++)
		f[i]=0;
	for(int i=1;i<m;i++)
	{
		for(int j=1;j<n;j++)
			f[j]+=down(j,i);
		for(int j=2;j<n;j++)
			f[j]=min(f[j],f[j-1]+right(j,i));
		for(int j=n-2;j>=1;j--)
			f[j]=min(f[j],f[j+1]+right(j+1,i));
	}
	ll ans=0x7fffffffffffffffll;
	for(int i=1;i<n;i++)
		ans=min(ans,f[i]+down(i,m));
	printf("%lld\n",ans);
	return 0;
}
posted @ 2018-04-23 15:18  ywwyww  阅读(280)  评论(0编辑  收藏  举报