UOJ Logo zgjkt的博客

博客

【bzoj2007】海拔

2015-05-15 13:42:20 By zgjkt

题目大意

YT市是一个规划良好的城市,城市被东西向和南北向的主干道划分为$n\times n$个区域。YT城市中包括$(n+1)\times (n+1)$个交叉路口和$2\times n\times(n+1)$条双向道路,每条双向 道路连接主干道上两个相邻的交叉路口。

小Z作为市长统计到了每条道路的人流量$w_i$,然而每个点都有一定的海拔$h_i(0\leqslant h_i\leqslant 1)$,那么经过一条道路需要耗费的体力值为$w_i\times max(0,h_{终点}-h_{起点})$

已知起点为右上角,海拔为0,终点为左下角,海拔为1,求最少耗费多少体力值。

顺便给出样例的图

数据范围

$1\leqslant n\leqslant 500,0\leqslant 流量\leqslant 1,000,000$且所有流量均为整数。


题解

找出图中海拔最高的点,如果它不是起点,那么总是可以把它调小使得结果更优。那么最高点最后就必然为终点。同理可知最低点必然为起点。那么图中有没有可能存在小数使得结果更优呢?同样可以把小数向上或向下调整到1或0使得代价不增。

所有海拔为0的点必然互相连通。为什么呢?显然,四面连接1的0如果使它取1必然使结果更优。同理可推海拔为1的点。

把每边人流量设为这条边的流量,求出割,分成海拔分别为0和1的两块。这时耗费体力值最少。

这题的做法和【bzoj1001】狼抓兔子一样的,对偶图最小割等于新图最短路。只不过这题的边是单向边,那题是双向边。

求出新图,跑一边最短路就可以了。


程序

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<queue>
#define For(i,l,r) for(int i=l;i<=r;i++)
#define goedge(i,x) for(int i=last[x];i;i=e[i].next)
#define inf 0x3f3f3f3f
using namespace std;

int n,cnt,et,s,t,x;
struct edge{int to,next,c;}e[2005000];
int d[250500],last[250500],flag[250500];
inline void addedge(int u,int v,int c){e[++et].to=v;e[et].c=c;e[et].next=last[u];last[u]=et;}

inline void spfa(){
    queue <int> q;memset(d,inf,sizeof(d));
    d[s]=0;q.push(s);flag[s]=1;
    while (!q.empty()){
        int h=q.front();q.pop();flag[h]=0;
        goedge(i,h)
            if (d[h]+e[i].c<d[e[i].to]){
                d[e[i].to]=d[h]+e[i].c;
                if (!flag[e[i].to]){q.push(e[i].to);flag[e[i].to]=1;}
            }
    }
}

int main(){
    scanf("%d",&n);s=0;t=n*n+1;
    For(i,1,n+1) For(j,1,n){
        int c;scanf("%d",&c);++cnt;
        int u=cnt,v=cnt-n;
        if (i==1) v=t;if (i==n+1) u=s;
        addedge(u,v,c);
    }cnt=0;
    For(i,1,n) For(j,1,n+1){
        int c;scanf("%d",&c);if (j!=1) ++cnt;
        int u=cnt,v=cnt+1;
        if (j==1) u=s;if (j==n+1) v=t;
        addedge(u,v,c);
    }cnt=0;
    For(i,1,n+1) For(j,1,n){
        int c;scanf("%d",&c);++cnt;
        int u=cnt-n,v=cnt;
        if (i==1) u=t;if (i==n+1) v=s;
        addedge(u,v,c);
    }cnt=0;
    For(i,1,n) For(j,1,n+1){
        int c;scanf("%d",&c);if (j!=1) ++cnt;
        int u=cnt+1,v=cnt;
        if (j==1) v=s;if (j==n+1) u=t;
        addedge(u,v,c);
    }cnt=0;
    spfa();
    printf("%d\n",d[t]);
    return 0;
}

评论

暂无评论

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。