1. 程式人生 > 實用技巧 >[BalticOI 2011]Switch the Lamp On

[BalticOI 2011]Switch the Lamp On

題目

原題連結

解說

先從建圖說起。

顯然如果一個格子內符號為 \ ,那麼我們不需要轉動元件就可以從左上角走到右下角,即從左上角走到右下角走了一條邊權為\(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;
}

幸甚至哉,歌以詠志。