P2196 挖地雷(采用dfs 暴力搜索 / 采用顺推 DP)

P2196 挖地雷
题目提供者
Huangc
难度
普及/提高-
NOIp提高组
2001(或之前)

题目描述
在一个地图上有NN个地窖(N \le 20)(N≤20),每个地窖中埋有一定数量的地雷。同时,给出地窖之间的连接路径。当地窖及其连接的数据给出之后,某人可以从任一处开始挖地雷,然后可以沿着指出的连接往下挖(仅能选择一条路径),当无连接时挖地雷工作结束。设计一个挖地雷的方案,使某人能挖到最多的地雷。

输入格式
有若干行。

第11行只有一个数字,表示地窖的个数NN。

第22行有NN个数,分别表示每个地窖中的地雷个数。

第33行至第N+1N+1行表示地窖之间的连接情况:

第33行有n-1n−1个数(00或11),表示第一个地窖至第22个、第33个、…、第nn个地窖有否路径连接。如第33行为1 1 0 0 0 … 011000…0,则表示第11个地窖至第22个地窖有路径,至第33个地窖有路径,至第44个地窖、第55个、…、第nn个地窖没有路径。

第44行有n-2n−2个数,表示第二个地窖至第33个、第44个、…、第nn个地窖有否路径连接。

… …

第n+1n+1行有11个数,表示第n-1n−1个地窖至第nn个地窖有否路径连接。(为00表示没有路径,为11表示有路径)。

输出格式
有两行

第一行表示挖得最多地雷时的挖地雷的顺序,各地窖序号间以一个空格分隔,不得有多余的空格。

第二行只有一个数,表示能挖到的最多地雷数。

输入输出样例
输入
5
10 8 4 7 6
1 1 1 0
0 0 0
1 1
1
输出
1 3 4 5
27

输入有点坑,图必须是单向边,就是有向图,这道题直接用dfs搜索即可,累积地雷数,即结点的权值,记得在结尾判死路,即dfs的最终点的时候进行判断大小(总值),不得不说落谷的ac让人特别有成就感0.0
在这里插入图片描述
注意第一次遍历的时候,也需要更新已访问数组和暂存记录的数组!!,具体细节看代码注释。
方法一

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
int N;//个数  地窖
int maxx;//地雷最大值
int dijiao[100];//地窖的地雷量
int cn[100][100] = {0};//点的连接
bool test[100] = {false};//防止重复访问结点
int P_num = 0;//路径的大小
int ans_num;//路径的大小
int ans[100];//最终路径
int P[100];//暂存路径
//无路可走
int stop(int ii) {
	for(int i = 1; i<=N; i++) {
		if(cn[ii][i]&&!test[i])
			return false;
	}
	return true;//无路可走
}
void dfs(int index,int dep,int sum) {
	if(stop(index)) {
		if(maxx<sum) {
			maxx = sum;
			for(int i = 0; i < P_num; i++) {
				ans[i] = P[i];
			}
			ans_num = P_num;
		}
	}
	for(int i =1; i<=N; i++) {
		if(cn[index][i]&&!test[i]) {
			test[i] = 1;
			P[P_num++] = i;
			dfs(i,dep+1,sum+dijiao[i]);
			P_num--;
			test[i] = 0;
		}
	}
}
int main() {
	scanf("%d",&N);
	maxx = 0;
	for(int i = 1; i <= N; i++) {
		scanf("%d",&dijiao[i]);
	}
	//题目的意思是单向边,而且1-->2345只有从小到大的情况
	//4到5,就是最终的边了
	for(int i =1; i <= N-1; i++) {
		for(int j =i+1; j<=N; j++) {
			scanf("%d",&cn[i][j]);
		}
	}
	//此题采用dfs深度搜索即可。
	for(int i =1; i <= N; i++) {
		test[i] = 1;
		P[P_num++] = i;
		dfs(i,1,dijiao[i]);//结点,深度,地雷量
		P_num--;
		test[i] = 0;
	}
	for(int i = 0; i < ans_num; i++) {
		if(i != ans_num-1)
			printf("%d ",ans[i]);
		else
			printf("%d\n",ans[i]);

	}
	printf("%d\n",maxx);

}

方法二
采用顺推,用f数组代表达到此节点的最大扫雷数,用一个pre记录前驱结点,每次扫描i的前驱结点j,更新最大值f,然后最后再把i结点本身的值加到f【i】上面来,接着不断扫描每个结点即可,记得记下那个最多扫雷数的最终结点。还有pre f 初始化都必须为0,前者用于判断结束,后者用于第一次比较

部分样例甚至比搜索慢,不过当数据集大了,肯定是dp快

#include<iostream>
#include<bits/stdc++.h>
#include<iostream>
using namespace std;

int N;
int pre[100] = {0};
int f[100] = {0};
int g[100][100] = {0};
int w[100] = {0};///地雷数
void print(int k) {
	if(pre[k] == 0) {
		printf("%d",k);
		return;
	}
	print(pre[k]);
	printf(" %d",k);
}
int main() {
	scanf("%d",&N);
	for(int i = 1; i <= N; i++) {
		scanf("%d",&w[i]);
	}
	for(int i = 1; i < N; i++) {
		for(int j = i+1; j <= N; j++) {
			scanf("%d",&g[i][j]);
		}
	}
	//扫描每个结点
	int ans = 0;
	int k = 0;
	for(int i = 1; i <= N; i++) {
		for(int j = 1; j <= N; j++) {
			if(g[j][i]&&f[j]>f[i]) {
				//f j 已经是积累值了  一开始f i 为0
				f[i] = f[j];
				pre[i] = j;
			}
		}
		f[i] += w[i];
		if(f[i] > ans) {
			ans = f[i];
			k = i;
		}
	}
	print(k);
	printf("\n%d",ans);
	printf("\n");
}

发布了154 篇原创文章 · 获赞 4 · 访问量 8620

猜你喜欢

转载自blog.csdn.net/weixin_38023259/article/details/105386645
今日推荐