2019.6.7 一场搜索专题的考试【including 洛谷·血色先锋队,入门OJ·兴建高铁,珠光宝气阁

这次分数还好。但全是搜索题还没上200就有点打击人了……【本狸才177QAQ

血色先锋队/血色敢死队

传送门:洛谷P1332 & 入门OJ P2259

Description

邪魔天国领主复活了,他的天灾军团将要重新夺回世界。血色敢死队的领主们组织了一支敢死队前往加西亚大陆。
孤立于联盟和部落的敢死队很快就遭到了天灾军团的重重包围。现在他们只好聚集了起来,成为一个方阵来抵抗天
国领主的围剿。更糟糕的事情降临了,血色敢死队成员之中有人感染上了亡灵瘟疫,如果不设法组织瘟疫扩散,他
们很快就会遭到灭顶之灾。军团方阵是一个N行M列的矩阵,每个单元是一个血色敢死队的成员。感染瘟疫的人,每
过一个小时,就会向四周扩散瘟疫,直到所有人全部感染上瘟疫。血色敢死队已经开始调查造成瘟疫的源头。令人
震惊的是,这次瘟疫是有预谋的。血色敢死队的内部出现了叛徒,这个无耻的叛徒投靠了天灾军团,想要毁灭血色
敢死队!血色敢死队的领主们议会讨论,发现你就是那个叛徒!在你的行踪败露之前,要尽快完成天国领主的命令
,并且逃离血色敢死队。一开始你就掌握了感染源的位置,你的任务是算出血色敢死队的领主们感染瘟疫的时间,
并且将它报告给天国领主,以便于对血色敢死队的下一轮围剿。

Input

第1行:四个整数N,M,A,B,表示军团矩阵有N行M列。有A个感染源,B为血色敢死队中领主的数量。
接下来A行:每行有两个整数x,y,表示感染源在第x行第y列。
接下来B行:每行有两个整数x,y,表示领主的位置在第x行第y列。
1<=M,N<=500 1<=A,B<=M*N

Output

第1至B行:每行一个整数,表示这个领主感染瘟疫的时间,输出顺序与输
入顺序一致。如果某个人的位置在感染源,那么他感染瘟疫的时间为0。

Sample Input

5 4 2 3
1 1
5 4
3 3
5 3
2 4

Sample Output

3
1
3

Sol

题意很明显——a个源点,层层扩散,b个首领,问你各自被波及到的时间。

bfs板子题!恩。直接把a个点push进queue里去,取出来更新整张图的时间即可。

直接上AC代码!

#include<bits/stdc++.h>
#define maxn 505
#define maxa 250010
using namespace std;
int read()
{
    int x = 0, ch = getchar();
    while(!isdigit(ch)) ch = getchar();
    while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
    return x;
}
 
int n, m, a, b;
int virus[maxa][2], ans[maxa];
int g[maxn][maxn], ques[maxa][2];
int dir[4][2] = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};
int dis[maxn][maxn];
struct node
{
    int x, y, dis;
    node() {}
    node(int tx, int ty, int d) {x = tx, y = ty, dis = d;}
};
 
bool in(int x, int y) {return 0 < x && x <= n && 0 < y && y <= m;}
 
queue<node> q;
void bfs()
{
    node now;
    register int tx, ty;
    while(q.size())
    {
        now = q.front(); q.pop();
        for(int i = 0; i < 4; i++)
        {
            tx = now.x + dir[i][0], ty = now.y + dir[i][1];
            if(in(tx, ty) && !g[tx][ty] && now.dis + 1 < dis[tx][ty])
            {   
                g[tx][ty] = 1;//这里g相当于vis
                dis[tx][ty] = now.dis + 1;
                q.push(node(tx, ty, now.dis + 1));
            }
        }
    }
}
 
int main()
{
    memset(ans, 0x3f, sizeof ans);
    memset(dis, 0x3f, sizeof dis);
    n = read(), m = read(), a = read(), b = read();
    register int tmp1, tmp2;
    for(int i = 1; i <= a; i++)
        tmp1 = read(), tmp2 = read(), q.push(node(tmp1, tmp2, 0)), dis[tmp1][tmp2] = 0;//dis也顺便初始化
         
    for(int i = 1; i <= b; i++)
        ques[i][0] = read(), ques[i][1] = read();//把问题存下来离线处理,当然也可以先bfs再读入问题在线
         
    bfs();
     
    for(int i = 1; i <= b; i++) printf("%d\n", dis[ques[i][0]][ques[i][1]]);
    return 0;
}

 

兴建高铁

Description

中央钦定在HA省建立国家级中心城市群,刚获得国家拨款兴建高铁,高铁的起止城市是中央决定的,中途可能经过
若干城市。根据国家拨款的政策,国家将负担费用最大的两个区间,其余的必须由HA省负担。假如高铁线路中途只
经过一个城市,国家只负担费用较大的区间。假如是直达的,国家将不负担任何费用。你被省里钦定为这个高铁兴
建项目的总工程师,必须规划出一条高铁线路,使得HA省负担的费用最少。当然,路线上每个城市最多只经过一次

Input

第一行是一个正整数n,n<=50代表每对城市之间的高铁建设费用估算
(注意并非每对城市之间的建设费用都进行了估算)。
接下来n行是用空格分隔的三个整数s,e,c。s和e代表城市的编码,
高铁的起点和终点城市分别是编码为0和1,其余的城市依次按顺序编码。
c<=1000,是在s和e之间建设费用估算,从s到e与从e到s的建设费用是相同的。

Output

输出只有一行,格式为c1c2…cmcost,各数字用一个空格分隔,代
表高铁线路规划和省负担的费用。
ci代表城市编码(注意c1=0,cm=1),cost是费用。我们保证输入肯定
有解,如果有多个解,输出当中经过城市最少的解,如果仍有多个解,则输出
当中按字典序排列最小的解。

Sample Input

12
0 7 10
8 1 2
1 9 7
1 7 10
8 4 5
3 4 8
6 3 12
5 6 15
6 9 7
2 3 1
0 2 3
5 0 14

Sample Output

0 2 3 4 8 1 6

Sol

求点0到点1的路径,及去掉最多两条边后的最少花费——很好,最短路!上最短路分层图!

然后就WA了一半。【??!!!

至今都死不瞑目为什么不能用最短路分层图QAQ我觉得这份代码没毛病但是连样例都过不了……或者说,样例里的分层图上的最短路都是……错的……【这里本来是存了size存了路径并且判断了dis相等时更优路径选择的,但是发现去掉后和选上没有什么区别就暂时去掉了……样例还是跑不出来……】

#include<bits/stdc++.h>//无向图 输入有解 城市最少 字典序最小 
#define maxn 5500
using namespace std;
int read()
{
	int x = 0, ch = getchar();
	while(!isdigit(ch)) ch = getchar();
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x;
}

int n = 5000, m;
struct edge
{
	int to, w, nxt;
	edge() {}
	edge(int tt, int ww, int nn) {to = tt, w = ww, nxt = nn;}
}e[maxn << 4];

int head[maxn << 4], k = 0;
void add(int u, int v, int w)
{
	e[k] = edge(v, w, head[u]);
	head[u] = k++;
}

int dis[maxn << 3], fa[maxn << 3], size[maxn << 3];
bool vis[maxn << 3];
void spfa()//from 0 to 1 + (n << 1)
{
	memset(dis, 0x3f, sizeof dis); dis[0] = 0;
    priority_queue<pair<int, int> > q;
    q.push(make_pair(0, 0));
    register int u, v;
    while(q.size())
    {
        u = q.top().second; q.pop();
        if(vis[u]) continue; vis[u] = 1;
        for(int i = head[u]; ~i; i = e[i].nxt)
        {
            v = e[i].to;
            if(dis[u] + e[i].w < dis[v])
            {
                dis[v] = dis[u] + e[i].w;
                fa[v] = u;
                q.push(make_pair(-dis[v], v));
            }
        }
    }
}

int ans[maxn << 1];
int main()
{
//	freopen("in.txt", "r", stdin);
//	freopen("out.txt", "w", stdout);
	memset(head, -1, sizeof head);
	m = read();
	register int u, v, w;
	for(int i = 1; i <= m; i++)
		u = read(), v = read(), w = read(), 
		add(u, v, w), add(v, u, w), 
		add(u, v + n, 0), add(u + n, v + (n << 1), 0),
		add(v, u + n, 0), add(v + n, u + (n << 1), 0),
		add(u + n, v + n, w), add(u + (n << 1), v + (n << 1), w),
		add(v + n, u + n, w), add(v + (n << 1), u + (n << 1), w);
	
	for(int i = 1; i <= (n << 2); i++) fa[i] = i;
	spfa();
	
	u = 1 + (n << 1), v = 0;
	while(u != 0) ans[++v] = u % n, u = fa[u];
	if(v == 2)
	{
		u = 1 + n, v = 0;
		while(u != 0) ans[++v] = u % n, u = fa[u];
		ans[++v] = 0;
		for(int i = v; i >= 1; i--) printf("%d ", ans[i]);
		printf("%d\n", dis[1 + n]);
		return 0;
	}
	
	else if(v == 1)
	{
		u = 1, v = 0;
		while(u != 0) ans[++v] = u % n, u = fa[u];
		ans[++v] = 0;
		for(int i = v; i >= 1; i--) printf("%d ", ans[i]);
		printf("%d\n", dis[1]);
		return 0;
	}
	
	ans[++v] = 0;
	for(int i = v; i >= 1; i--) printf("%d ", ans[i]);
	printf("%d\n", dis[1 + (n << 1)]);
} 
/*
7
0 2 10
0 3 6
2 4 5
3 4 3
3 5 4
4 1 7
5 1 8
*/

然后就只好 老实巴交 地按照题解的思路写了dfs……似乎这么想就是一个dfs的板子题了呢……用邻接矩阵存图,一来方便二来可以天然保证求出来的字典序最小。然后一顿爆搜……顺便判断一下走了多少条边,存一下最长边和次长边……

上代码QAQ

#include<bits/stdc++.h>
#define maxn 105
using namespace std;
int n, g[maxn][maxn];
int p[maxn], fa[maxn], ans = 0x3f3f3f3f, cnt = maxn;
bool vis[maxn];
void dfs(int now, int max1, int max2, int len, int cost)//变量名还是很清楚的吧……
{	
	p[len] = now;//len为当前走了几条边/城市数 - 1。因为len = 0的时候存的就是0所以无所谓
	if(now == 1)//走到目的地了
	{
		if(len > 2) cost = cost - max1 - max2;//特判
		else if(len == 2) cost -= max1;
		
		if(cost < ans || (cost == ans && len < cnt))//花费更少,路径更短,都更优
		{
			memcpy(fa, p, sizeof p);
			ans = cost, cnt = len;
		}
		return;
	}
	
	for(int i = 1; i <= n; i++)
	{
		if(g[now][i] && !vis[i])
		{//超级暴力的搜索,一看就能懂吧,都只是在处理最长和次长边
			vis[i] = true;
			if(g[now][i] > max1) dfs(i, g[now][i], max1, len + 1, cost + g[now][i]);
			else if(g[now][i] > max2) dfs(i, max1, g[now][i], len + 1, cost + g[now][i]);
			else dfs(i, max1, max2, len + 1, cost + g[now][i]);
			vis[i] = false;//回溯
		}
	}
}


int main()
{
	scanf("%d", &n);
	register int u, v, w;
	for(int i = 1; i <= n; i++)
		scanf("%d%d%d", &u, &v, &w), g[u][v] = g[v][u] = w;
		
	dfs(0, 0, 0, 0, 0);
	
	printf("0");
	for(int i = 1; i <= cnt; i++) printf(" %d", fa[i]);
	printf(" %d\n", ans);
	return 0;
}

试题开头写了是搜索专题的。那又怎样?也不是只能用搜索啊,我还是死不瞑目啊……

珠光宝气阁

Description

金鹏皇朝的藏宝图被陆小凤拿走了。狡猾的陆小凤加为了躲避大金鹏王的追杀,他们并没有把《藏宝图》带回黄石
镇,而是把它藏了起来。但是终究是金鹏家族技高一筹,运用朱停的最新研究成果“静电放射探测器”,已经发现
了陆小凤的藏身之地。现在金鹏王得到消息,陆小凤就把《藏宝图》放在了珠光宝气阁的最深处。更坏的消息是,
金鹏王的女儿上官丹凤已经发现了入口。金鹏王想要把《藏宝图》偷回来。然而陆小凤布置了严密的防御,不过所
幸的是,陆小凤从朱停那购买了电磁监视器。无论盗贼的潜行技巧有多么高明,只要一接近它,就会出发警报。只
有破坏它的供电系统,才能电磁监视器悄无声息得失效。现在,“静电放射探测器”已经为我们生成了一张地图,
它可以告诉你整个地下城的布局结构,包括每一个电磁监视器的位置,及其供电装置的位置。陆小凤的地下城可以
被描述为一个 N*N 的表格,城市的入口在(1,1)处,目标《藏宝图》在(N,N)处。每个单元格可能是一片空地、一
个障碍物、一个亡灵卫士、一个电磁监视器、或者一个的供电装置。从入口处开始,每步你只能上、下、左、右移
动到相邻的一个单元格,不可以停留在原地。你只能进入空地,或者失去供电系统的电磁监视器的位置,或者摧毁
供电装置。你不能移动到障碍物上,也不能进入亡灵卫士的视线中。陆小凤的亡灵卫士可以监视自己所在单元格以
及上下左右共五格的位置,而且他们的视线可以重叠。你不能杀死亡灵卫士,也不能被他们发现。每个电磁监视器
的供电装置可能存在,也可能无法破坏或者根本不存在。一个供电装置也可能会对应零个、一个或多个电磁监视器
,意味着摧毁它,对应的所有电磁监视器都会失效。(1,1)和(N,N)一定是可以通行的。金鹏王要求你在执行任务之
前首先给出一个计划书,即要求算出至少一共需要多少步,才能拿到金鹏王朝的《藏宝图》。

Input

第 1 行,两个整数 N, M。表示地图大小为 N*N,供电装置的数量为 M。
第 2-N+1 行,每行 N 个整数,每个整数 i 可能是 0,-1,-2 或者一个正整数。
i=0 表示该位置为一块空地,i=-1 表示该位置为一个障碍物,i=-2 表示该位置为一个亡灵卫士。
如果 i 是一个属于[1,M]的正整数,则表示该位置为一个供电装置,其编号为 i。
如果 i 是一个大于 M 的正整数,则表示该位置为一个电磁监视器,它的电力由编号为 i-M 的供电装置提供。
2<=N<=50 0<=M<=16

Output

一个整数,为拿到《藏宝图》所需的最少的步数。
如果不能到达输出-1.

Sample Input

6 2
0 0 0 -2 -1 2
-1 0 0 0 -1 0
-2 0 0 0 3 3
-2 0 0 -1 -1 4
0 -1 0 0 -1 0
1 0 0 0 -1 0

Sample Output

24

 

 

Sol

看到题目那么长,第一反应就是大脑停滞了不想看题……但其实翻译过来了就很简单——给你个地图,上边有障碍物,能影响上下左右中的障碍物,监视器和解除监视器装置。并且一个装置可对应多个监视器【看装置的编号就知道每个监视器的供电装置是唯一的】。

本人第一反应——处理不来这个装置!因为如果没有装置【那15%的数据】,那这就是一个纯bfs。那么有装置呢?似乎每次解锁了装置过后都有走回头路的必要。所以想到了dfs,但是写着写着就又写不下去了……临时改成了idfs过了那15%的数据QAQ

其实因为你每次解锁了装置后,之前的路走过与否都没有意义了,就仿佛是进入了另一层地图。所以就有了分层搜索的概念了。

我们用到状压,用一个二进制数表示所有的装置已经解锁了哪些当遇到监视器时按位与判断能不能前进即可。对于每一种状态,我们都有一层地图,互不影响。这么下来我们要开2^16个地图,6w多个……恭喜你,这个题的内存有512MB所以这么开不会MLE。可是也有23wKB的样子。其实我们可以开一个hash表,遇到一个新的状态就为其开一个地图,最后我开了2w多个地图,也能过的。而遇到装置,我们看有没有遇到过解锁这个装置后的状态,有则继续无则cnt++,存入hash表,继续bfs按位或上当前装置的那一位后的状态。其余的操作,就是bfs了。

有一个细节需要注意——当你读入了-2时,你不能就直接上下左右中变成-1就完了,因为可能读到下一行的时候会被覆盖掉。处理方式自己思考啦~【我卡了十几分钟才发现QAQ

看代码——

#include<bits/stdc++.h>//一个供电多个 供电的只有m个,编号唯一 
#define maxn 55
using namespace std;
int g[maxn][maxn], n, m;//0=空地,-1=障碍,-2=卫士=5个障碍 
bool vis[26000][maxn][maxn];//26446
int dir[4][2] = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};
struct node
{
	int x, y, dis, key;//坐标,距离和状态
	node() {}
	node(int xx, int yy, int dd, int kk) {x = xx, y = yy, dis = dd, key = kk;}
};

int cnt = 0;
map<int, int> mp;
bool in(int x, int y) {return 0 < x && x <= n && 0 < y && y <= n;}
void bfs()
{
	queue<node> q; q.push(node(1, 1, 0, 0));
	node now;
	register int tx, ty, key;
	while(q.size())
	{
		now = q.front(), q.pop();
		key = now.key;//key为状态
		if(now.x == n && now.y == n) {printf("%d\n", now.dis); return;}
		for(int i = 0; i < 4; i++)
		{
			tx = now.x + dir[i][0], ty = now.y + dir[i][1];
			if(in(tx, ty) && !vis[mp[key]][tx][ty] && g[tx][ty] != -1)
            //注意vis里使用key的时候一定是hash表mp里对应的层数
			{
				if(0 < g[tx][ty] && g[tx][ty] <= m)//case 1: 是供电装置
				{
					vis[mp[key]][tx][ty] = 1;
					if(!mp[key | (1 << g[tx][ty] - 1)]) mp[key | (1 << g[tx][ty] - 1)] = ++cnt;
                    不在hash表里,那就存进去
					vis[mp[key | (1 << g[tx][ty] - 1)]][tx][ty] = 1;//-1是因为从第0位开始用的
					q.push(node(tx, ty, now.dis + 1, key | (1 << g[tx][ty] - 1)));
				}
				
				else if(g[tx][ty] > m)//case 2:是监视器
				{
					if(key & (1 << g[tx][ty] - m - 1)) vis[mp[key]][tx][ty] = 1, q.push(node(tx, ty, now.dis + 1, key));
				}
				
				else vis[mp[key]][tx][ty] = 1, q.push(node(tx, ty, now.dis + 1, key));
			}
		}
	}
}


int main()
{
	scanf("%d%d", &n, &m);
	int tmp;
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= n; j++)
		{
			scanf("%d", &tmp);
			if(tmp >= 0 && g[i][j] == -1) continue;
            //如果已经标记了不能走了,那么只要不是-2【-2可以继续影响四周】,就不能覆盖了
			if(tmp == -2)
			{
				g[i][j] = -1;
				if(i > 0) g[i - 1][j] = -1;
				if(j > 0) g[i][j - 1] = -1;
				g[i + 1][j] = -1; 
				g[i][j + 1] = -1;
			}
			else g[i][j] = tmp;
		}
	
	bfs();
	return 0; 
}

看似很复杂,解法也很麻烦,但是这后面两个搜索其实代码的可行性都很高,一般思路清楚后都能一遍过的。

然后本狸就双又被细节卡住了QAQ 【←蠢

不过还好的是能在看了题解的思路过后代码自己写出来……

迎评:)
——End——

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值