Ceoi2014 bzoj 4356 考托福又不会考 国家集训队作业题

考托福又不会考

Description

给出一个 N *M的网格图, 有一些方格里存在城市, 首都在网格图的左上角。(左上角存在一个城市)
方格的边上可以修墙,修每一面墙都有不同的费用。
接下来要修墙,修不同的墙需要不同的费用,修出来的所有墙必须是一堵可以自交的环形的墙,如下图是一个可行的例子:
这里写图片描述
红色的边为要修墙的边,红色的边组成一个自交的环形,例子是最优解,为 22

Data Constraint

1 <=N, M <=400
1 <=所有边权<=109

Solution

集训队原来都喜欢做那么变态的题,改了我一个晚上有多,有毒……
首先,抛出一个结论,这个可以自交的环包含了从左上角走到每个城市左上角的最短路径,解释一下为什么
这里写图片描述

D 为某个城市的左上角,蓝色路径为最短路径,红色为环的一部分,此时,环的红色部分并没有包含最短路径,但此时我们可以发现,可以把从B走向 C 的红色路径改为从B走向 C 的蓝色路径,这样显然答案不会更劣。改为蓝色路径后,环便包含了蓝色路径。

接下来进行下一步操作,将网格图上的(N+ 1 )*(M+ 1 )的每个点拆成4个点,如下图所示

这里写图片描述

答案所需要的自交的环为在这些被拆开的点之间的边组成的路径之一。
接下来我们对这些点连上合法的边,跑一遍最短路即为答案了。

如图,网格点 A ,B, C ,D都分别被拆成了 4 个点,这四个点的作用可以理解成转变方向,(记A 1 号点为A1,表示其他点的方法类似,为了下文叙述的方便),红色边为最短路径上的边。
对于每一个网格点, 1 2 2 4 3 4 1 3两两之间连上一条双向边,边权为 0
特别地,对于被最短路径阻断的相邻的两点不能 连边,因为跑出来的环必须包含最短路径(不能穿过最短路径),如上图所示,A1 A2 A3 A4 B1 B2 B2 B4 这些点对之间就不能连边,只是为了保证环能够包含最短路径。
对于像 A4 C3 所属的网格点在网格图上相邻的点,之间要连上一条边权为 Cost ( A ,C)的边,类似的, A3 B1 之间要连上一条边权为 Cost ( A ,B)的边。

还有一个注意的地方,一个城市必须被环包含,为了能够保证每个城市都被包含,在这个城市内的点都不能连边,下图为一个连边模式图,红色的虚线是不能连的边,蓝色的实线是可以连的边(画的好丑啊╥﹏╥):
这里写图片描述

还有一点注意的,左上角的 2 3都不能和左上角的 1 连边。
最后以左上角的2为起点,跑一边最短路,从左上角的 2 到左上角的3的最短路径即为答案了(显然啊)。

跑最短路当然要用 dijkstra 算法,不然无法保证时间不会超限。

Code

\\好长啊╥﹏╥
#pragma GCC optimize(3)
#pragma G++ optimize(3)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>

#define fo(i,j,l) for(int i=j;i<=l;i++)
#define fd(i,j,l) for(int i=j;i>=l;i--)

using namespace std;
typedef long long ll;
const ll N=415,M=N*N,K=4*M,V=K*8,maxn=1e12;
ll zd[V];
struct Comp{
    bool operator ()(int a,int b)
    {return zd[a]!=zd[b] ? zd[a]<zd[b] : a<b ;}     
};
set<int,Comp> op;
int ac[N][N],up[N][N],ne[V],la[V],lb[V],co[V];
int bz[M][5],city[N][N],zy[V];
int n,m,j,k,l,i,o,p;

int read()
{
    int o=0;char ch=' ';
    for(;ch<'0'||ch>'9';ch=getchar());
    for(;ch>='0'&&ch<='9';ch=getchar())o=o*10+ch-48;
    return o;
}

void dij1(int kk)
{
    zd[1]=0; op.insert(1); 
    fo(i,2,kk)zd[i]=maxn,op.insert(i);
    fo(i,1,kk-1)if(!op.empty()){
        int k=*op.begin(); op.erase(k);
        int ii=(k-1)/m+1,oo=(k-1)%m+1;
        if(ii-1){
            int bh=k-m,p=up[ii-1][oo];
            if(zd[k]+p<zd[bh])op.erase(bh),zd[bh]=zd[k]+p,op.insert(bh),zy[bh]=m;
        }
        if(ii<n){
            int bh=k+m,p=up[ii][oo];
            if(zd[k]+p<zd[bh])op.erase(bh),zd[bh]=zd[k]+p,op.insert(bh),zy[bh]=-m;
        }
        if(oo-1){
            int bh=k-1,p=ac[ii][oo-1];
            if(zd[k]+p<zd[bh])op.erase(bh),zd[bh]=zd[k]+p,op.insert(bh),zy[bh]=1;
        }
        if(oo<m){
            int bh=k+1,p=ac[ii][oo];
            if(zd[k]+p<zd[bh])op.erase(bh),zd[bh]=zd[k]+p,op.insert(bh),zy[bh]=-1;
        }
    }
}

void dij2(int kk)
{
    op.clear();
    fo(i,3,kk)zd[i]=maxn,op.insert(i);
    zd[2]=0; op.insert(2);
    fo(i,2,kk-2)if(!op.empty()){
        int k=*op.begin(); op.erase(k);
        for(int y=la[k];y;y=ne[y])
        if(zd[k]+co[y]<zd[lb[y]]){
            op.erase(lb[y]); zd[lb[y]]=co[y]+zd[k];
            op.insert(lb[y]);
        }
    }
}

void llb(int a,int b,int c)
{ne[++o]=la[a]; lb[o]=b; la[a]=o; co[o]=c;}

int main()
{
    cin>>n>>m;
    fo(i,1,n)fo(l,1,m)city[i][l]=read();
    fo(i,1,n)fo(l,1,m+1)up[i][l]=read();
    fo(i,1,n+1)fo(l,1,m)ac[i][l]=read();
    int kk=0;
    n++; m++;
    dij1(n*m);
    fo(i,1,n-1)
    fo(l,1,m-1)
    if(city[i][l]){
        int u=(i-1)*m+l;
        bz[u][3]=bz[u][4]=1;
        bz[u+1][1]=bz[u+1][4]=1;
        bz[u+m][2]=bz[u+m][3]=1;
        bz[u+m+1][1]=bz[u+m+1][2]=1;
        while(zy[u]!=0){
            int y=u+zy[u];
            if(zy[u]==-1)bz[u][1]=bz[y][3]=1;
            if(zy[u]==1)bz[u][3]=bz[y][1]=1;
            if(zy[u]==-m)bz[u][2]=bz[y][4]=1;
            if(zy[u]==m)bz[u][4]=bz[y][2]=1;
            u=y;
        } 
    }
    fo(i,1,n)
    fo(l,1,m)
    if(i*l!=1)
    {
        int y=(i-1)*m+l;
        int u1=(y-1)*4+1,u2=u1+1,u3=u2+1,u4=u3+1;
        if(!bz[y][1])llb(u1,u3,0),llb(u3,u1,0);
        if(!bz[y][2])llb(u1,u2,0),llb(u2,u1,0);
        if(!bz[y][3])llb(u2,u4,0),llb(u4,u2,0);
        if(!bz[y][4])llb(u3,u4,0),llb(u4,u3,0);
    }
    if(!bz[1][4])llb(3,4,0),llb(4,3,0);
    if(!bz[1][3])llb(2,4,0),llb(4,2,0);
    fo(i,1,n-1)
    fo(l,1,m-1){
        int y1=(i-1)*m+l-1,y2=y1+1;
        int y3=i*m+l-1,y4=y3+1;
        y1*=4; y2*=4; y3*=4; y4*=4;
        llb(y1+2,y2+1,ac[i][l]); llb(y2+1,y1+2,ac[i][l]);
        llb(y2+4,y4+2,up[i][l+1]); llb(y4+2,y2+4,up[i][l+1]);
        llb(y3+4,y4+3,ac[i+1][l]); llb(y4+3,y3+4,ac[i+1][l]);
        llb(y1+3,y3+1,up[i][l]); llb(y3+1,y1+3,up[i][l]);
        if(!city[i][l]){
            llb(y1+4,y2+3,ac[i][l]); llb(y2+3,y1+4,ac[i][l]);
            llb(y1+4,y3+2,up[i][l]); llb(y3+2,y1+4,up[i][l]);
            llb(y3+2,y4+1,ac[i+1][l]); llb(y4+1,y3+2,ac[i+1][l]);
            llb(y2+3,y4+1,up[i][l+1]); llb(y4+1,y2+3,up[i][l+1]);
        }
    }
    dij2(n*m*4);
    printf("%lld",zd[3]);
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值