洛谷P2196例题分析

[NOIP1996 提高组] 挖地雷(原题)

题目描述

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

输入格式

有若干行。

\(1\)行只有一个数字,表示地窖的个数\(N\)

\(2\)行有\(N\)个数,分别表示每个地窖中的地雷个数。

\(3\)行至第\(N+1\)行表示地窖之间的连接情况:

\(3\)行有\(n-1\)个数(\(0\)\(1\)),表示第一个地窖至第\(2\)个、第\(3\)个、…、第\(n\)个地窖有否路径连接。如第\(3\)行为\(1 1 0 0 0 … 0\),则表示第\(1\)个地窖至第\(2\)个地窖有路径,至第\(3\)个地窖有路径,至第\(4\)个地窖、第\(5\)个、…、第\(n\)个地窖没有路径。

\(4\)行有\(n-2\)个数,表示第二个地窖至第\(3\)个、第\(4\)个、…、第\(n\)个地窖有否路径连接。

… …

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

输出格式

有两行

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

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

样例 #1

样例输入 #1

5
10 8 4 7 6
1 1 1 0
0 0 0
1 1
1

样例输出 #1

1 3 4 5
27

例题分析

思路一:动态规划

不难发现要想从j到i有两个条件:

  • 两个地窖间有路相互连通(题目似乎并不允许自己挖一条……)
  • i的地雷数与目前j的总地雷数之和大于i的最大地雷数,即目前j点的最大价值与i点的价值这和大于i点的最大价值(要不然你走了干嘛……)

所以状态转移方程很简单:dp[i]=max(dp[i],dp[j]+a[i])
随后是稍微不一样的地方:它要求输出路径
如果用搜索,路径很好保存。但如果是动规的话还需要再维护一个数组pre作为每一个点的前驱(就是你从哪来的)。
比如从a到b,它符合上述两个条件,那么可以更新b点的最大价值,并且将b的前驱设为a点,那么输出时只要倒序输出即可。

AC代码如下:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
bool flag[500][500];		//两个点是否连通
int a[500],dp[500],pre[500],pos,n,ans;		//a为总价值,pre为前驱,dp为一维动规数组
void dfs(int num){		//输出用的函数
	if(pre[num])	dfs(pre[num]);
	printf("%d ",num);
	return ;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)	scanf("%d",&a[i]);
	for(int i=1;i<n;i++){
		for(int j=i+1;j<=n;j++){
			cin>>flag[i][j];
		}
	}
	dp[1]=a[1];		//初始价值
	for(int i=2;i<=n;i++){
		dp[i]=a[i];
		for(int j=i-1;j>0;j--){
			if(flag[j][i]&&dp[i]-a[i]<dp[j]){
				//不用状态转移方程是因为此处要记录前驱点
				dp[i]=dp[j]+a[i];
				pre[i]=j;
			}
		}
		if(ans<dp[i]){
			ans=dp[i];
			pos=i;
		}
	}
	dfs(pos);
	printf("\n%d",ans);
	return 0;
}

思路二:dfs暴搜

暴力骗分,但对于较强数据建议dp,或者做例如记忆化之类的优化
可以参考灰名红名大佬的第一篇题解
AC代码如下:

点击查看代码
#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<algorithm>
#include<queue>
using namespace std;
bool f[21][21];//记录是否有路径相连
int a[21];//记录地雷数
int path[21],ans[21],cnt;//path记录路径,ans记录答案,cnt记录走了多少个点
bool b[21];//记录该点是否走过
int n;
int maxx;//记录挖的最大地雷数
bool chck(int x)//检查是否还能继续往下挖
{
	for(int i=1;i<=n;i++)
	{
		if(f[x][i]&&!b[i]) return false;
 	}
 	return true;
}
void dfs(int x,int stp,int sum)//x记录现在位置,stp记录走了几个点,sum记录挖的地雷数
{
	if(chck(x))
	{
		if(maxx<sum)//更新最大值和路径
		{
			maxx=sum;
			cnt=stp;
			for(int i=1;i<=stp;i++)
			ans[i]=path[i];	
		}
		return ;
	}
	for(int i=1;i<=n;i++)//寻找下一个能去的地方
	{
		if(f[x][i]&&!b[i])
		{
			b[i]=1;//标记走过
			path[stp+1]=i;//记录路径
			dfs(i,stp+1,sum+a[i]);
			b[i]=0;//回溯
		}
		
	}
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	cin>>a[i];
	for(int i=1;i<n;i++)
	for(int j=i+1;j<=n;j++)
	{
		cin>>f[i][j];//这里是单向边,题目没啥清楚,导致我调了半个小时;
	}
	for(int i=1;i<=n;i++)
	{
		b[i]=1;
		path[1]=i;//记录起点
		dfs(i,1,a[i]);
		b[i]=0;
	}
	for(int i=1;i<=cnt;i++)
	cout<<ans[i]<<' ';
	cout<<endl<<maxx;
	return 0;
}
posted @ 2022-11-09 20:03  Nebulary  阅读(75)  评论(0编辑  收藏  举报