【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;
}

猜你喜欢

转载自www.cnblogs.com/ywwyww/p/8919303.html