题目链接:http://codeforces.com/contest/1105/problem/D
题目大意:
给一个地图,地图上有#(不可走),数字,和点组成,每种数字代表一种帮派。然后按照1-2-3.。。。。的顺序扩张领地,然后再1 2 3的扩张。每个帮派有一个扩张速度,直到最后无地可走,输出每个帮派各有多少领地。
思路:(读者可以从绿色字体开始往下读,前半部分为错误思路的分析)
起初我想的是可以用双端队列,把扩张速度可以看作扩张次数,然后如果双端队列的队首元素还有大于1的可扩张次数,就入队首,并且扩张次数减一,直到扩张次数为1,入队尾。这样做想法可行,但是实现的时候我们会发现一个尴尬的问题,后到一个点如果还有的扩张次数比原来的大,但是那个点已经被vis掉了怎么办??好解决。只要vis数组记录一下这个点的可扩张次数,如果这个点是“自己之前扩张的领地”且,可扩张次数比这个大,就再次入队。想法又可行了,但是超时在test20了。
这是怎么回事呢?比如这组样例
5 5 2
100 1
2.1.1
1.1.1
1.1.1
1.1.1
1.1.1
如果用的双端队列,那么我们从第一个1开始搜索,也就是(1,3)的位置。。搜索开始!!!(出队过程忽略不写)
1.(1,2)入队首(1,4)入队首,两个点的可扩张次数为99
2.(2,4)入队首.。。。(3,4)入队首。。。(等等!!为什么是(2,4)入队不是(2,2),要知道我们在不断地入队首,所以又不断地从队首取元素,所以双端队列的队首部分成为了一个栈!!!)
3. 。。。。直到(5,4)入队然后此处省略若干字。。(5,2)入队。。到目前为止我们只考虑了一个点,而这些走过的点在下边又会碰到更优的点(存在更近的源点)从而重复入队
于是开始胡思乱想。。。。。。。(以下是才正解)
只要我解决了双端队列的头是个栈的问题是不是就可以优化了,并且不需要重复入队。。好就这么干。取消双端队列改为用两个队列q和q1,bfs的时候在q队列中的元素的可扩张次数都为他的最大的扩张次数,也就是每轮的开始,而q1,就是每轮每个帮派的辅助队列以便进行一下的s次扩充。我们从q中出一个然后把能到的入到q1中,直到换q中换下一个帮派的时候我们,把q1中存的上个帮派存的结点全部扩充。如果扩充次数大于1就还入q1,如果不是1就入队q。但是注意!!!我们的q空了之后,还要清空q1一遍,不然会漏点。
我们的q1辅助队列帮我们解决了双端队列的队首是栈且可重复入队的弊端。虽然表面上多加了一个循环但是却做出了优化。很棒。。而且这样的最大好处就是入队条件不用那些 花里胡哨的只需要判断这个点在不在地图里头,和是否走过就可以。不用判断重复入队,因为这就是简简单单(并不)地模拟了题目的扩张步骤。
所以双端队列+栈应该也可以过(纯粹口胡。)
#include<iostream>
#include<string.h>
#include<algorithm>
#include<cmath>
#include<stdio.h>
#include<queue>
#define ll long long
#include<vector>
using namespace std;
const int MAXN=1005;
int n,m,p;
char C[1005][1005];
int ans[20];
int vis[1005][1005];
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
struct Point
{
int x,y,s,num;//x,y为坐标,s为扩张次数,num为帮派
Point(){}
Point(int x1,int y1,int s1,int num1){x=x1;y=y1;s=s1;num=num1;}
};
bool check(int x,int y)
{
if(x>=1&&x<=n&&y>=1&&y<=m)return 1;
else return 0;
}
int sp[20];
vector<Point>v[20];
void bfs()
{
queue<Point>q,q1;//q为每轮扩张的源点队列,q1为每个帮派每次扩长的辅助队列
for(int i=1;i<=p;i++){//用vector存第一次的地图上的扩张源点
int len=v[i].size();
for(int j=0;j<len;j++){
q.push(v[i][j]);
}
}
int lun=1;
while(!q.empty()){
Point now=q.front();
q.pop();
if(now.num!=lun){
while(!q1.empty()){
Point now2=q1.front();
q1.pop();
for(int i=0;i<4;i++){
int xx=now2.x+dx[i];
int yy=now2.y+dy[i];
if(check(xx,yy)&&!vis[xx][yy]){
vis[xx][yy]=1;
C[xx][yy]=now2.num+'0';
if(now2.s>1)q1.push(Point(xx,yy,now2.s-1,now2.num));
else q.push(Point(xx,yy,sp[now2.num],now2.num));
}
}
}
}
lun=now.num;
for(int i=0;i<4;i++){
int xx=now.x+dx[i];
int yy=now.y+dy[i];
if(check(xx,yy)&&!vis[xx][yy]){
vis[xx][yy]=1;
C[xx][yy]=now.num+'0';
if(now.s>1)q1.push(Point(xx,yy,now.s-1,now.num));
else q.push(Point(xx,yy,sp[now.num],now.num));
}
}
}
while(!q1.empty())//此时q空了,但是q1没空记得再清一次
{
Point now2=q1.front();
q1.pop();
for(int i=0;i<4;i++){
int xx=now2.x+dx[i];
int yy=now2.y+dy[i];
if(check(xx,yy)&&!vis[xx][yy]){
vis[xx][yy]=1;
C[xx][yy]=now2.num+'0';
if(now2.s>1)q1.push(Point(xx,yy,now2.s-1,now2.num));
else q.push(Point(xx,yy,sp[now2.num],now2.num));
}
}
}
}
int main()
{
memset(ans,0,sizeof(ans));
memset(vis,0,sizeof(vis));
scanf("%d%d%d",&n,&m,&p);
for(int i=1;i<=p;i++)
scanf("%d",&sp[i]);
for(int i=1;i<=n;i++){
scanf("%s",C[i]+1);
for(int j=1;j<=m;j++){
if(C[i][j]!='.')vis[i][j]=1;
if(C[i][j]>='1'&&C[i][j]<='9'){
int now=C[i][j]-'0';
v[now].push_back(Point(i,j,sp[now],now));
}
}
}
bfs();
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++){
if(C[i][j]>='0'&&C[i][j]<='9')ans[C[i][j]-'0']++;
// cout<<C[i][j]<<"+";
}
// cout<<endl;
}
for(int i=1;i<=p;i++)
{
printf("%d ",ans[i]);
}
printf("\n");
}