[CEOI2014] The Wall【最短路】
洛谷P6545 [CEOI2014] The Wall
Description
在一張 \(n\times m\) 的網格圖上有若干關鍵格子,且格子 \((1,1)\) 必定是關鍵格子。你需要建造一座城牆使得它包括所有關鍵格子,具體的,城牆是沿著網格線上的一條連續閉曲線,每經過一段網格線一次,就會產生對應的代價。請你最小化這一代價。
\(n,m\le 400\),對所有花費 \(v\) 有 \(1\le v\le 10^9\)。
Solutions
首先給出引理:
最終的城牆一定會包含左上角的點到每個關鍵格子左上角的點的最短路。
證明:考慮一個合法的建造城牆方案,如果它不含某條最短路,那麼這條最短路會被城牆分為若干部分,在牆內的與在牆外的交替出現,此時如果將城牆擴充套件使得在牆外的部分都進入牆中,那麼答案一定不劣:
例如圖中綠色為一條最短路,\(v\) 為一個關鍵格子,藍色為城牆,那麼此時將城牆擴張到包含黃色區域,那麼根據最短路的定義,新包括進來的這一段綠色城牆作為最短路,一定比原來那部分的城牆花費代價更少。同時,擴張城牆只擴大了區域,不會影響已經包含進去的格子。因此這樣擴充套件城牆答案一定不劣。
於是我們先跑一遍最短路,找到左上角點到每個關鍵格子左上角點的最短路,那麼問題轉化為了找一條從 \(1\) 號格子出發的包括所有關鍵點且不穿過最短路樹上的邊時的最小權閉合迴路。
可以考慮將每個格點拆成 \(4\) 個小點,從左上開始順時針依次編號為 \((0,1,2,3)\),它們互相連邊權為 \(0\) 的邊組成一個 \(4\)
如果一條邊在最短路上,被強制要求經過,如果這條邊是豎直的,就讓上端點的 \((2, 3)\) 邊不連,下端點的 \((0,1)\) 不連;如果這條邊是水平的,就讓左端點的 \((1,2)\) 不連,右端點的 \((0,3)\) 不連。
對於原圖上的一條正常邊,就讓其兩端點相對的兩個分點對應連比權為該邊權值的邊即可。最終問題是求從 \((1,1)\) 格子的 \(0\) 號點出發的最小閉合迴路,可以改為將 \(0\) 號點刪去,求從 \(1\)
最終複雜度 \(\mathcal O(nm\log nm)\)。
Code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=410,M=N*N*4;
int n,m,r[N][N],c[N][N],p[N][N],first[M],cnt,tp,f[M];
struct node{
int v,nxt;ll w;
}e[M<<2];
inline int id(int tp,int x,int y){return tp*(n+1)*(m+1)+(x-1)*(m+1)+y;}
inline void add(int u,int v,ll w){e[++cnt].v=v;e[cnt].w=w;e[cnt].nxt=first[u];first[u]=cnt;}
inline void Add(int u,int v,ll w){
// if(tp) cout<<u<<" "<<v<<" "<<f[u]<<" "<<f[v]<<endl;
if(tp&&(f[u]||f[v])) return;
add(u,v,w);add(v,u,w);
}
ll dis[M];bool vis[M];
int tot,last[M];
int pd[N][N][2];
inline ll dijkstra(int s,int t){
priority_queue<pair<ll,int> > q;
memset(dis+1,0x3f,sizeof(ll)*(tot));
memset(vis+1,0,sizeof(bool)*(tot));
memset(last+1,0,sizeof(int)*(tot));
dis[s]=0;q.push(make_pair(0,s));
while(!q.empty()){
int u=q.top().second;q.pop();
if(vis[u]) continue;vis[u]=1;
for(int i=first[u];i;i=e[i].nxt){
int v=e[i].v;
if(dis[u]+e[i].w<dis[v]){
dis[v]=dis[u]+e[i].w;last[v]=u;
q.push(make_pair(-dis[v],v));
}
}
}
return dis[t];
}
inline void limit(int u,int v){
int x1=(u-1)/(m+1)+1,y1=(u-1)%(m+1)+1,x2=(v-1)/(m+1)+1,y2=(v-1)%(m+1)+1;
if(x1==x2) pd[x1][min(y1,y2)][0]=1;
else pd[min(x1,x2)][y1][1]=1;
}
int main(){
scanf("%d%d",&n,&m);
f[1]=1;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j){
scanf("%d",&p[i][j]);
if(p[i][j])
f[id(2,i,j)]=f[id(3,i,j+1)]=f[id(1,i+1,j)]=f[id(0,i+1,j+1)]=1;
}
tot=id(3,n+1,m+1);
for(int i=1;i<=n;++i)
for(int j=1;j<=m+1;++j)
scanf("%d",&r[i][j]);
for(int i=1;i<=n+1;++i)
for(int j=1;j<=m;++j)
scanf("%d",&c[i][j]);
for(int i=1;i<=n+1;++i)
for(int j=1;j<=m+1;++j){
if(i<n+1) Add(id(0,i,j),id(0,i+1,j),r[i][j]);
if(j<m+1) Add(id(0,i,j),id(0,i,j+1),c[i][j]);
}
dijkstra(id(0,1,1),id(0,n+1,m+1));
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
if(p[i][j]){
int now=id(0,i,j);
while(now!=id(0,1,1)){
limit(last[now],now);
now=last[now];
}
}
cnt=0;
memset(first+1,0,sizeof(int)*(tot));tp=1;
for(int i=1;i<=n+1;++i)
for(int j=1;j<=m+1;++j){
if(!pd[i-1][j][1]) Add(id(0,i,j),id(1,i,j),0);
if(!pd[i][j][0]) Add(id(1,i,j),id(2,i,j),0);
if(!pd[i][j][1]) Add(id(2,i,j),id(3,i,j),0);
if(!pd[i][j-1][0]) Add(id(3,i,j),id(0,i,j),0);
}
for(int i=1;i<=n;++i)
for(int j=1;j<=m+1;++j)
Add(id(3,i,j),id(0,i+1,j),r[i][j]),Add(id(2,i,j),id(1,i+1,j),r[i][j]);
for(int i=1;i<=n+1;++i)
for(int j=1;j<=m;++j)
Add(id(1,i,j),id(0,i,j+1),c[i][j]),Add(id(2,i,j),id(3,i,j+1),c[i][j]);
printf("%lld\n",dijkstra(id(1,1,1),id(3,1,1)));
return 0;
}