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");
}