[BalticOI 2011]Switch the Lamp On
阿新 • • 發佈:2020-08-02
題目
解說
先從建圖說起。
顯然如果一個格子內符號為 \ ,那麼我們不需要轉動元件就可以從左上角走到右下角,即從左上角走到右下角走了一條邊權為\(0\)的邊,而如果想從左下角走到右上角則需要轉動一次元件,也可以抽象為從左上角走到右下角走了一條邊權為\(1\)的邊。符號為 / 時與之同理。
圖建好之後我們就可以直接跑最短路了……嗎?
如果我們放縱一下自己開\(O2\)的話確實可以輕鬆水過,但是這並不是我們希望看到的結果。
下面展示一下硬跑迪傑斯特拉和\(SPFA\)的優秀戰果。
迪傑斯特拉:\(TLE\) \(88\)分
SPFA:\(TLE\) \(86\)分
這不尷尬了嗎[托腮] [托腮]
顯然我們需要進行某些改良才行。不難發現在這道題中邊權只有\(0\)和\(1\)兩種,我們應該充分利用這一特點。顯然我們應該儘可能走邊權為\(0\)的邊,這時候我們想起\(SPFA\)中有一種叫雙端佇列優化的東西,那麼我們只需將其稍作修改,通往目前點的邊權若為\(0\)則將其放在前端,否則放在後端,這樣就保證了每個點的進隊數量儘量少,並且可以保證第一次搜到終點時其答案就是最小的,可以直接退出。
程式碼
#include<bits/stdc++.h> using namespace std; const int maxn=500+3; int head[maxn*maxn],tot,n,m,dis[maxn*maxn]; struct edge{ int to,next,w; }e[maxn*maxn*4];//一定注意邊的數量!!! void add(int a,int b,int w){ e[++tot].to=b; e[tot].w=w; e[tot].next=head[a]; head[a]=tot; } void spfa(){ deque<int> q; memset(dis,0x3f,sizeof(dis)); dis[1]=0; q.push_front(1); while(!q.empty()){ int u=q.front(); q.pop_front(); for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(dis[u]+e[i].w<dis[v]){ dis[v]=dis[u]+e[i].w; if(v==(n+1)*(m+1)) return; if(!e[i].w) q.push_front(v); else q.push_back(v); } } } } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ char tmp; scanf(" %c",&tmp); if(tmp=='/'){ add((i-1)*(m+1)+j+1,i*(m+1)+j,0); add(i*(m+1)+j,(i-1)*(m+1)+j+1,0); add((i-1)*(m+1)+j,i*(m+1)+j+1,1); add(i*(m+1)+j+1,(i-1)*(m+1)+j,1); } else{ add((i-1)*(m+1)+j+1,i*(m+1)+j,1); add(i*(m+1)+j,(i-1)*(m+1)+j+1,1); add((i-1)*(m+1)+j,i*(m+1)+j+1,0); add(i*(m+1)+j+1,(i-1)*(m+1)+j,0); } } } spfa(); if(dis[(n+1)*(m+1)]==0x3f3f3f3f) printf("NO SOLUTION\n"); else printf("%d\n",dis[(n+1)*(m+1)]); return 0; }
幸甚至哉,歌以詠志。