这道题其实不难,思路就是:将走过的路用直线连起来,就立刻想到了构造一颗树,而树的边权是任意两点间的最短距离,需要用bfs搜索,最后用最小生成树模板套一下就行了。但我主要遇到了三个问题,做了很久才AC:
1. 样例研究了很久没看懂,尤其是第一个,直到突然发现只有在S点和A点处才能“分队”,而不是任何位置。
2. 写bfs的时候,我一开始写的是O(n^2)的复杂度,即任意两点之间都用一次bfs,判TLE了。。然后看了别人的博客,发现只用O(n)的复杂度即可,即每个点进行一次bfs,搜索它到其余各点的最短路径;其中需要标记已经搜过的两点,否则就会加两次边。
3. 最坑的一点:输入x,y之后可能会有空格,但是没有空行。而我一开始处理的全是空行,利用cin.getline(),结果一直WA,怎么也找不到问题。。然后随手试了一下别人用的gets(),过了。。。
AC代码如下:
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
using namespace std;
const int INF=0x3f3f3f3f;
#define pb push_back
#define mkp make_pair
typedef pair<int,int>pp;
const int MAX=55;
const int MAXN=106;
const int MAXM=MAXN*MAXN/2;
int x,y;//长,宽
char s[MAX][MAX];
int n,m;//点,边
vector<pp>vt;//点
map<pp,int>mp;//点的编号
bool sign[MAXN][MAXN];//标记两点间是否有边
struct Edge
{
int u,v,w;
}e[MAXM];
void init()
{
memset(s,'\0',sizeof(s));
memset(e,0,sizeof(e));
vt.clear();mp.clear();
n=0;m=0;x=0,y=0;
memset(sign,false,sizeof(sign));
}
int f[MAXN];
int Find(int x)
{
if(x==f[x])
return x;
else
return Find(f[x]);
}
bool cmp(struct Edge a,struct Edge b)
{
return a.w<b.w;
}
void kruskal()
{
for(int i=0;i<n;i++)
f[i]=i;
sort(e,e+m,cmp);
int cnt=0,ans=0;
for(int i=0;i<m;i++)
{
int u=e[i].u,v=e[i].v,w=e[i].w;
int u0=Find(u),v0=Find(v);
if(u0!=v0)
{
f[v0]=u0;
cnt++;
ans+=w;
}
if(cnt==n-1) break;
}
printf("%d\n",ans);
}
void bfs(pp x); //某点到其他点之间的距离
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
init();
scanf("%d%d",&x,&y);
/*while(cin.getline(s[0],MAX))
if(s[0][0]!='\n'&&s[0][0]!='\0') //处理空行
break;
//A null character('\0') is automatically appended even if an empty string is extracted.
for(int i=1;i<y;i++)
cin.getline(s[i],MAX);*/
gets(s[0]);
for(int i=0;i<y;i++)
gets(s[i]);
/*for(int i=0;i<y;i++)
cout<<"i="<<i<<" "<<s[i]<<endl;*/
for(int i=0;i<y;i++)
for(int j=0;j<x;j++)
if(s[i][j]=='S'||s[i][j]=='A')
{
pp tmp=mkp(i,j);
vt.pb(tmp);
mp[tmp]=n+1;//从0开始在判断访问时会出bug
n++;
}
for(int i=0;i<n;i++)
bfs(vt[i]);
//cout<<n<<" "<<m<<endl;
kruskal();
}
return 0;
}
int vis[MAX][MAX];//标记是否访问且记录步数
int d[4][2]={{0,-1},{0,1},{1,0},{-1,0}};
void bfs(pp st) //某点到其他点之间的距离
{
queue<pp>que;
while(!que.empty())
que.pop();
memset(vis,0,sizeof(vis));
vis[st.first][st.second]=1;//0可能会出bug
que.push(st);
while(!que.empty())
{
pp k=que.front();
que.pop();
int dis=vis[k.first][k.second];
for(int i=0;i<4;i++)
{
int a=k.first+d[i][0],b=k.second+d[i][1];
if(a>=0&&a<y&&b>=0&&b<x&&!vis[a][b]&&s[a][b]!='#')
{
pp t=mkp(a,b);
if(mp[t]&&sign[mp[st]][mp[t]]==false&&sign[mp[t]][mp[st]]==false)//加边
{
e[m].u=mp[st]-1;e[m].v=mp[t]-1;//顶点从0开始标号
e[m].w=dis;//因为起点vis=1,所以此处不需要+1
m++;
sign[mp[st]][mp[t]]=true;sign[mp[t]][mp[st]]=true;
//注意此处不需要break!!
}
vis[a][b]=dis+1;
que.push(t);
}
}
}
}
大三上的比赛结束后,这两年我就没怎么刷题了,脑速手速也基本“残废”。也许以后更有没时间,但希望能保持多敲代码的习惯吧。