51nod2373 最短路和

2373 最短路和

 小D生活的城市可以抽象为一个H×W的网格图,左上角为位置(0,0),右下角为位置(H-1,W-1)。

每个位置可以上下左右走,但是不能越出边界,也就是每次走到的位置(X,Y)都要满足0≤X<H,0≤Y<W。

现在这个网格图上有K个障碍点是不能通行的。现在小D想知道,对于网格图上任意两个不是障碍的格子,

他们的最短路是多少。为了输出方便,你只需要输出所有答案的和即可。具体来说,你需要算出,

网格图上,任意一对均不是障碍的位置,他们的最短路的和。

一个位置(A,B)到另外一个位置(X,Y)的最短路的定义是:从(A,B)出发,走到(X,Y),中途不经过任何障碍点,

且不越过边界的所有路径中,最少经过的边数。注意一对位置只需要算一遍。 

输入

第一行包含三个整数H,W,K。
接下来K行,每行给定一个坐标(X,Y),代表一个障碍点的位置。

对于20%的数据,H×W≤5000
对于40%的数据,H,W≤10000
对于60%的数据,H,W≤10^6
对于100%的数据,H,W≤10^9,N≤50,0≤x_i<H,0≤y_i<W,保证给定的障碍点不重复,所有非障碍点都连通,至少存在一个非障碍点。

输出

输出一个整数,表示答案。答案请对10^9+7取模。

输入样例

4 4 3
0 2
2 1
3 2

输出样例

258

分析:

可以注意到题目的障碍数非常少,联想到要把大块的空白区域缩成一块。首先考虑缩行。

对于连续的两行i,i+1,假如i,i+1这两行上,一个障碍都没有,那么对于所有在i行上方的起点s,

到所有i+1行下方的终点t,都会经过这两行恰好1次。由于这两行是完全一样的,

我们可以把他们压缩成一行。同时把每个位置标记为(对应原来的2个位置),在这个子问题的答案下,

加上所有上方到下方的点,这两行对答案的贡献(可以直接算出来),就是原问题的答案了。

 进一步的,可以发现,对于连续的一段[l,r],假如行l到行r,都是没有障碍的。

我们都可以把他们压缩成一行。我们可以模拟l和l+1合并,再到l+2,…一直到r。现在要做到快速把[l,r]合并,

就需要把过程中每两行对答案的贡献的公式加起来化简(自行推导,最后是一个平方和的形式)。

把行合并完后再对列做同样的事情。最后最多只会剩下2N行与2N列。 剩下的问题就比较简单了。

现在的每个格子不再是一个格子,而是代表原来的多个。这个在求答案时乘一下就好。

当前的网格图大小是100*100级别。可以枚举每个格子,做一遍宽搜,求出到其他所有点的最短距离。

假如起点对应数量为c_s,终点对应数量为c_t,距离为d,他们对答案的贡献就是c_s×c_t×d。

 总的复杂度就是O(N^4)。关键思想就是离散化。

放代码:

#include <bits/stdc++.h>
using namespace std;
const int MOD = 1e9+7;
const int N = 110;
int fpow(int x,int p){
    int rs=1;
    while(p){
        if(p&1) rs=1LL*rs*x%MOD;
        x=1LL*x*x%MOD; p>>=1;
    }
    return rs;
}
struct Node{
    int x,y;
    Node(int _x=0,int _y=0){
        x=_x; y=_y;
    }
};
vector<Node> ob;
int x[N],y[N],h,w,k,n,m,inv2,inv6;
int cx[N],cy[N],c[N][N],cn[N][N];
int d[N][N],dx[4]={0,-1,1,0},dy[4]={-1,0,0,1};
bool check(Node p){
    return p.x>=0&&p.x<n&&p.y>=0&&p.y<m&&d[p.x][p.y]==-1&&!cn[p.x][p.y];
}
int merge(int a,int b,int d,int n){
    int rs=1LL*a*b%MOD*n%MOD;
    b=(b-a+MOD)%MOD;
    rs=(rs+1LL*b*n%MOD*(n+1)%MOD*inv2%MOD*d%MOD)%MOD;
    rs=(rs-1LL*n*(n+1)%MOD*(2*n+1)%MOD*inv6%MOD*d%MOD*d%MOD+MOD)%MOD;
    return rs;
}
Node Q[N*N]; int front,tail;
void bfs(Node cur){
    Node nex(0,0); front=0,tail=0;
    for(int i=0;i<n;++i)
        for(int j=0;j<m;++j) d[i][j]=-1;
    d[cur.x][cur.y]=0;
    Q[tail++]=cur;
    while(front<tail){
        cur=Q[front]; front++;
        for(int i=0;i<4;++i){
            nex.x=cur.x+dx[i];
            nex.y=cur.y+dy[i];
            if(check(nex)){
                d[nex.x][nex.y]=d[cur.x][cur.y]+1;
                Q[tail++]=nex;
            }
        }
    }
}
int ans,tp,tot;
int main(){
    inv2=fpow(2,MOD-2); inv6=fpow(6,MOD-2);
    scanf("%d %d %d",&h,&w,&k);
    for(int i=0;i<k;++i){
        scanf("%d %d",&x[i],&y[i]);
        x[i+k]=x[i]+1; y[i+k]=y[i]+1;
        ob.push_back(Node(x[i],y[i]));
    }
    n=m=2*k; tot=(1LL*h*w-k+MOD)%MOD;
    x[n++]=0; x[n++]=h; y[m++]=0; y[m++]=w;
    sort(x,x+n); n=unique(x,x+n)-x-1;
    sort(y,y+m); m=unique(y,y+m)-y-1;
    for(auto p:ob){
        p.x=lower_bound(x,x+n,p.x)-x;
        p.y=lower_bound(y,y+m,p.y)-y;
        cx[p.x]++; cy[p.y]++; cn[p.x][p.y]++;
    }
    for(int i=n-1,c=0;i>=0;--i){
        c=(c+1LL*(x[i+1]-x[i])*w%MOD-cx[i]+MOD)%MOD;
        ans=(ans+merge((tot-c+MOD)%MOD,c,w,x[i+1]-x[i]-1))%MOD;
    }
    for(int i=m-1,c=0;i>=0;--i){
        c=(c+1LL*(y[i+1]-y[i])*h%MOD-cy[i]+MOD)%MOD;
        ans=(ans+merge((tot-c+MOD)%MOD,c,h,y[i+1]-y[i]-1))%MOD;
    }
    for(int i=0;i<n;++i)
        for(int j=0;j<m;++j)
            c[i][j]=1LL*(x[i+1]-x[i])*(y[j+1]-y[j])%MOD;
    for(int i=0;i<n;++i){
        for(int j=0;j<m;++j){
            if(cn[i][j]) continue;
            bfs(Node(i,j));
            for(int p=0;p<n;++p){
                for(int q=0;q<m;++q){
                    if(cn[p][q]) continue;
                    tp=(tp+1LL*c[i][j]*c[p][q]%MOD*d[p][q])%MOD;
                }
            }
        }
    }
    tp=1LL*tp*inv2%MOD; ans=(ans+tp)%MOD;
    printf("%d\n",ans);
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值