BalticOI2013 Tracks in the Snow【搜尋-最短路-卡時空】
題目解析
啊咧,是一道卡時間卡空間的好題目(劃掉
我們可以先找到最後那隻小動物最多能走過的結點,就是和左上角相連的一整個四聯通連通塊。(以下所有連通塊都是指四聯通
然後發覺這個連通塊就可以讓所有的小動物隨便走了,因為無論如何走,最後總會被最後一隻小動物覆蓋。
那麼把和這個連通塊相連的其它連通塊相連,可以通過第一個連通塊樞紐一下就可以相互到達的連通塊之間可以由同一個小動物來走。
考場上剛開始想要用並查集來寫,先\(dfs\)一次把所有連通塊縮成一個點,然後再\(dfs\)連邊,算這些點到左上角連通塊代表的那個點的距離的最大值就是答案。
然而這樣時間和空間都不夠優秀(所以就自閉了,本來想拿個部分分結果我這個演算法寫錯了不太調得出來就開始重新想。
我們發現這個過程其實是在不斷擴充套件聯通塊的過程。
從最後一個小動物的足跡形成的連通塊開始,往相鄰的另外一種足跡擴充套件,表示第二隻小動物,形成一個新的大連通塊就是這兩隻小動物的足跡範圍,然後同理再次擴充套件,直到把所有的點都合在一起,答案就是擴充套件次數\(+1\)(加上最開始的連通塊
當時有點自暴自棄,沒有想到這就是接近正解的想法呢。
於是就可以先\(dfs\)第一個連通塊,相同的就繼續搜,不同的就丟到佇列裡(這就是我們下一隻小動物擴充套件的地方)。然後從佇列裡取結點開始搜,搜過的地方打上標記可以不用再搜,理論上是\(O(n^2)\)的時間複雜度。
然後你就可以得到\(90pts\)的好成績(霧(事實上,我考試的時候編號寫掛了,掛成了\(60pts\)
因為我寫的\(dfs\),會佔用棧空間導致本不富裕的空間限制雪上加霜。
所以可以寫\(bfs\)來規避掉這一點。
另外貼一個聽評講時的另外一種理解思路:
當某隻動物走過雪地後,左上角和右下角必定是這種動物的足跡,且這兩點之間有一個這種足跡形成的連通塊,不妨設是兔子\((R)\)。
很明顯,若只有\(R\),那麼只需要\(1\)只動物即可;如果存在\(F\),那麼至少也要\(2\)只動物。
考慮什麼情況下會有更多隻動物。
所有與起點相連通的\(R\),全都可以最後一隻兔子完成,而如果某個足跡\(R\)被\(F\)包圍成圈而無法與起點連通,那麼肯定需要提前有一隻兔子先走到這裡,再讓一隻狐狸覆蓋掉周圍的足跡,最後再讓一隻兔子走一遍,這樣就需要\(3\)
容易發現,這種情況還可能會巢狀,即\(R\)和\(F\)交替地形成圓圈包圍住另一種足跡,每多一圈就需要多一隻動物。
於是可以把問題轉化為:相鄰的同種足跡之間邊權為\(0\),異種足跡之間邊權為\(1\),求出起點到所有點的最短路徑,最短路徑的最大值\(+1\)即為最少需要的動物數量。
這種只有\(0/1\)邊權的問題是一個比較經典的可以用雙端佇列來解決的問題,把邊權為\(0\)的塞到前端,邊權為\(1\)的塞到後端。當然,寫兩個佇列分開放也可以,這個連通塊擴充套件的過程一定是交替的。
(不知道為啥打小動物的時候全程想到調查土壤小動物的豐富度,雖然這是兔子和狐狸,生物殺我qwq
►Code View
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
#define N 4005
#define M 16000005
#define INF 0x3f3f3f3f
#define LL long long
int rd()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return f*x;
}
const int dx[]={0,0,1,-1},dy[]={1,-1,0,0};
int n,m,ans;
char s[N][N];
bool vis[N][N];
queue<int>Q[2];
inline bool check(int i,int j)
{
if(i<1||i>n||j<1||j>m||s[i][j]=='.'||vis[i][j]) return 0;
return 1;
}
void bfs()
{
int now;
if(s[1][1]=='R') Q[0].push(1),now=0;
else Q[1].push(1),now=1;
while(!Q[now].empty())
{
while(!Q[now].empty())
{
int x=Q[now].front();Q[now].pop();
int j=x%m,i=x/m+1;
if(j==0) j=m,i--;
if(vis[i][j]) continue;
vis[i][j]=1;
for(int k=0;k<4;k++)
{
int x=i+dx[k],y=j+dy[k];
if(!check(x,y)) continue;
if(s[x][y]==s[i][j]) Q[now].push((x-1)*m+y);
else Q[now^1].push((x-1)*m+y);
}
}
now^=1;
ans++;
//這個佇列清完了 擴充套件另外一種顏色 答案++
}
}
int main()
{
freopen("track.in","r",stdin);
freopen("track.out","w",stdout);
n=rd(),m=rd();
for(int i=1;i<=n;i++)
scanf("%s",s[i]+1);
bfs();
printf("%d\n",ans);
return 0;
}
►Code View Ver.2 dfs MLE
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
#define N 4005
#define M 16000005
#define INF 0x3f3f3f3f
#define LL long long
int rd()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return f*x;
}
const int dx[]={0,0,1,-1},dy[]={1,-1,0,0};
int n,m,tot;
char s[N][N];
bool id[N][N];
queue<pair<int,int> >Q;
inline bool check(int i,int j)
{
if(i<1||i>n||j<1||j>m||s[i][j]=='.'||id[i][j]) return 0;
return 1;
}
inline void dfs(int i,int j,int num)
{
id[i][j]=1;
for(register int k=0;k<4;k++)
{
int x=i+dx[k],y=j+dy[k];
if(!check(x,y)) continue;
if(s[x][y]==s[i][j])
{
id[x][y]=1;
dfs(x,y,num);
}
else Q.push(make_pair((x-1)*m+y,num+1));
}
}
int main()
{
freopen("track.in","r",stdin);
freopen("track.out","w",stdout);
n=rd(),m=rd();
for(register int i=1;i<=n;i++)
scanf("%s",s[i]+1);
dfs(1,1,1);
int ans=1;
while(!Q.empty())
{
pair<int,int> x=Q.front();Q.pop();
int j=x.first%m,i=x.first/m+1;
if(j==0) j=m,i--;//Cao 這裡打掉了 慘掛30pts
if(!id[i][j])
dfs(i,j,x.second);
ans=max(ans,x.second);
}
printf("%d\n",ans);
return 0;
}
/*
5 8
FFR.....
.FRRR...
.FFFFF..
..RRRFFR
.....FFF
*/
/*
把連通塊從最開始的情況擴張
擴張多少次所有點在一個集合裡
*/