天梯賽 森森美圖
阿新 • • 發佈:2020-10-05
https://pintia.cn/problem-sets/994805046380707840/problems/994805047395729408
先說一下題意
在一個二維點陣上選兩個點s、t,這兩個點所在的直線將這個點陣分成兩部分
在這兩部分裡面分別找一條s到t的路徑(八連通),這兩條路徑構成一個閉環,最小化閉環的分數
注意直線所經過的點不屬於任何一部分,不能使用
閉環的分數定義為:
每個點都有一個分數,將閉環上的點的分數相加
同時,如果兩個點是斜著連通的,那還需要額外加上這兩個點分數和的(根號2-1)倍
踩的坑:
1、點的座標是從0開始計算的
2、向右為x軸,向下為y軸,即給出的點的座標第一個是列座標,第二個是行座標
3、如果路徑連續斜著經過的點有3個或更多個,是所有斜著相鄰連通的點對都有額外的加分,也就是中間斜著的會計算2次額外加分
這就是在每一個部分做一次spfa
如何判斷下一個點v是否在該部分:利用叉積
x到s看作是一個向量,x到t看作是一個向量
兩個向量做叉積
所有叉積<0的是一部分,所有叉積>0的是一部分,所有叉積=0的不屬於任何一部分
#include<queue> #include<cmath> #include<cstdio> #include<iostream> #include<algorithm> using namespacestd; #define N 101 int n,m; int mp[N][N]; int sx,sy,tx,ty; double pp; struct node { int x,y; }; int dx[8]={-1,1,0,0,-1,-1,1,1}; //上、下、左、右、左上、右上、左下、右下 int dy[8]={0,0,-1,1,-1,1,-1,1}; double d[N][N]; bool vis[N][N]; int tty; double ans; bool judge(int x,int y) //叉積判斷是否屬於同一部分 { int a1=sx-x,b1=sy-y;int a2=tx-x,b2=ty-y; if(tty==1) return (a1*b2-b1*a2<0); else return (a1*b2-b1*a2>0); } void bfs() { pp=sqrt(2)-1; for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) d[i][j]=2e9; queue<node>q; node now,nxt; now.x=sx; now.y=sy; d[sx][sy]=mp[sx][sy]; vis[sx][sy]=true; q.push(now); int nx,ny; double nd; while(!q.empty()) { now=q.front(); q.pop(); vis[now.x][now.y]=false; for(int i=0;i<8;++i) { nx=now.x+dx[i]; ny=now.y+dy[i]; if(nx<1 || nx>n || ny<1 || ny>m) continue; if(!judge(nx,ny) && (nx!=tx || ny!=ty)) continue; nd=d[now.x][now.y]+mp[nx][ny]; if(i>=4) nd+=(mp[nx][ny]+mp[now.x][now.y])*pp; //斜著額外的分數 if(nd<d[nx][ny]) { d[nx][ny]=nd; if(nx==tx && ny==ty) continue; if(!vis[nx][ny]) { nxt.x=nx; nxt.y=ny; q.push(nxt); vis[nx][ny]=true; } } } } ans+=d[tx][ty]; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) scanf("%d",&mp[i][j]); scanf("%d%d%d%d",&sy,&sx,&ty,&tx); sy++; sx++; ty++; tx++; tty=1; //標記現在是做哪一部分 bfs(); tty=2;//標記現在是做哪一部分 bfs(); printf("%.2lf",ans-mp[sx][sy]-mp[tx][ty]); //起始位置算了2次 }