dfs无需多讲,这里主要提一下用位运算表示草料占用
这是笔者的第一篇题解,说句实话确实有点激动,笔者之前没有学过竞赛,这是大一第一次接触编程,如果哪里不对,也请各位dalao批判指正。
本题所考查的知识点:
- dfs(深度优先搜索)
- 对一个已经用过的进行标记
这里的标记不是用于去除去那些已经选过的草料(完全可以通过运算方式去避免),这里是为了能够在最后输出那些草料。
这里主要说一下用位运算的方式,对不太大的样本进行保存,这一点更多的或许会用在dp(动态规划)上。
1、什么是位运算
大家可以看看下面这个网站:
位运算简析
2、本题的用法
谈到计数,大家可以想象一下数以二进制的方式在计算机内存储,以一个int数为例,int是32位,也就是说,其在计算机中,有32个小空格储存!
那么就可以把这32位看成32个草料
这就是基本思路。
具体的位运算实现,我们需要实现两个功能:
1、 添加一个(把某一位标记为使用)
use=use|(1<<i);
//这个是位运算保存使用状态的核心之一,或运算|可以把第i位赋值为1,标记为用过。
2、 复原(把一个标记为未被使用)
use=use&(~(1<<i));
关于这一段代码,需要描述的是,有两个主要的运算符,~和&,这里的思路主要是,把1移到滴i位上,在用取反运算符(~)取反后,就变为只有这一位是0,那么在&运算下,这一位将一定会变为0(标记为复原)。
3、 判断用了哪些
bool ck(int use)
{
for(int i=1;i<=v;i++)
{
int tt=use;int k=1;int no=0;
while(tt>0)
{
tt=tt>>1;
if(tt&1)
{
no+=a[k][i];
}
k++;
}
if(no<h[i]) return 0;
}
return 1;
}
解铃还须系铃人,将tt不断地右移,再用一个位运算中常用的取位判断就是(tt&1),可以判断tt当前位是否为1,如果是1,则代表用过,否则没有,不断重复此操作,直到全部为0.
好啦,有了以上的知识,这一部分就基本完成了,之后的dfs就可以看别的dalao的解析了。
AC代码:
#include<iostream>
using namespace std;
int a[100][26]={};//每种饲料,它的营养价值
int h[26]={};//我们需要的营养价值
int v,g;
int used=0;//已经使用的草料
int ans=100;//记录最终结果
int t[16]={};//笔者一开始放在dfs里面的错误遗迹,失败的眼泪
bool ck(int use)//这个函数是来判断当前情况是否已经完成任务,这里注意,笔者一开始把这个判断放在dfs里面写,果断超时,果然还是不太行。
{
for(int i=1;i<=v;i++)
{
int tt=use;int k=1;int no=0;
while(tt>0)
{
tt=tt>>1;
if(tt&1)
{
no+=a[k][i];
}
k++;
}
if(no<h[i]) return 0;
}
return 1;
}
void dfs(int x,int temp,int use)//正常不过的dfs,这里的x表示目前该从第几个草料开始取,temp记录当前总共取了多少草料,use就是对取的草料进行记录)
{
if(ck(use))
{
if(temp<ans)
{
ans=temp;
used=use;
}
return ;
}
if(temp>ans) return;//这里是一个剪枝,如果temp已经大于ans,那么久不需要再找了
if(x>g) return ;//对边缘条件的判断
for(int i=x;i<=g;i++)
{
use=use|(1<<i);//这个是位运算保存使用状态的核心之一,或运算|可以把第i位赋值为1,标记为用过。
dfs(i+1,temp+1,use);
use=use&(~(1<<i));//回溯
}
}
int main()
{
cin>>v;
for(int i=1;i<=v;i++)
{
cin>>h[i];
}
cin>>g;
for(int i=1;i<=g;i++)
{
for(int j=1;j<=v;j++)
{
cin>>a[i][j];
}
}
for(int i=1;i<=g;i++)//第一个取的草料是哪个,之后,只可以取这个草料的后面的草料,既不漏,又能化简运算。
{
int use=(1<<i);
dfs(i+1,1,use);
}
cout<<ans;int k=1;
while(used>0)
{
used=used>>1;
if(used&1) cout<<' '<<k;
k++;
}
return 0;
}