一道简单的DFS,但是让我写了好久,只能说我是醇比呜呜呜
思路:普通DFS,递归结束的条件是目前选择的牛饲料对应的每种维他命总和满足牛的需求
然后我们需要每次更新最优解
以下是AC代码:
#include<bits/stdc++.h>
using namespace std;
#define TLE ios::sync_with_stdio(0),cin.tie(0)
#define endl "\n"
#define qfor(i,n) for(int i=1; i<=n; i++)
const int N=110;
int g[N][N],v[N];//g[编号为n的饲料][第几种维他命],v[牛所需要的每种维他命目标]
int a,b; //a是维他命数目,b是饲料种树
bool st[N]; //存储转态,是否被标记
int path[N]; //数组存储当前dfs路径存储的答案
int lu[N]; //存储最优解答案
int d=2e5; //存储最优解的长度。因为每次要取min(d,u),所以先给d赋一个很大的值
int w[N]; //存储目前路径每种维他命的总和,判断是否满足牛的需要
bool pan(int x) //判断目前路径每种维他命的总和,判断是否满足牛的需要
{
memset(w,0,sizeof(w)); //清空数组先
for(int j=1; j<=b; j++)
{
for(int i=0; i<x; i++)
{
w[j] += g[path[i]][j];
}
if(w[j] < v[j])return false; //只要有一种维他命不满足就可以直接return false
}
return true; //走完了判断的循环说明这个时候每种维他命都满足牛的需要了
}
void dfs(int u){
if(d <= u)return; //如果不能更新最优解直接剪枝
//这里严格小于才更新最优解,因为这样的话就可以保存数字长度一样的时候的第一个解,在字典序中一定是靠前的
if(u > a)return; //如果越界了也结束
if(u <= a){ //没越界
if( pan(u) ){ //这种路径可以满足牛的维他命需求
d = u; //更新最优长度
for (int i = 0; i < d; i ++ )lu[i] = path[i]; //更新最优路径
return;
}
}
for(int r=path[u-1]; r<=a; r++) //这里很重要,决定了是80分还是100分
//遍历的起点从上一次的编号开始,这样可以避免:1-2-3 和 1-3-2 , 2-1-3 , 2-3-1 , 3-1-2, 3-2-1的重复查找
//这些重复的选择里面字典序最小的(1-2-3)会被第一个搜出来,其他重复的选择我们不需要,直接不看,这就让某两个TLE的测试点AC了
if(!st[r]) //这个点没有被标记过
{
path[u] = r; //存进路径
st[r] = true; //标记
dfs(u+1); //递归找下一层
st[r] = false; //回溯
path[u] = 0; //回溯
}
}
int main()
{
TLE;
memset(g,0,sizeof(g));
cin>>b;qfor(i,b)cin>>v[i];
cin>>a;
qfor(i,a){
qfor(j,b)cin>>g[i][j];
}
dfs(0); //从0根节点开始深搜
cout<< d <<" "; //输出最优解的数字多少
for (int i = 0; i < d; i ++ ) cout << lu[i] << ' '; //把存储最优解的数组输出
cout<<endl;
return 0;
}
tip1.这里的u指的是我们搜到的层数,对应的就是我们选择的饲料的种类多少
tip2.判断递归路径中的答案是否满足递归结束条件的部分我最开始写在了dfs里,并且写的很复杂。其实都可以,并不影响,但是长得很丑QAQ。
单独开一个函数写,漂亮好多了捏。
tip3.最重要的部分,就是在递归的循环部分的起始点,我最开始是从 1 到 a 遍历一遍找下一个点。但是这样很蠢,因为这样搜到的选择会有大量的重复选择,还是我们不需要的,这样直接让我两个测试点TLE了呜呜呜
tip4.有很多别的写法
tip5.祝看到这段话的你每天AC多多!提前祝你今天开开心心捏0W0