这周主要是看搜索专题练习题目,就目前所看题目大致总结了几个知识专题,在看题解的过程中学到一些新的知识点,并通过看博客了解了一些,但不太能熟练的运用,还需要多加练习。尝试着提交几道题,有些通过,但还有一些存在着答案或编程上的错误,比如数组需要重新声明问题,还需要继续修改。这一周的总结我想来分析一下通过部分题目总结出来的几个比较常用的知识方面:
1、剪枝算法分析
例题:
P1535 [USACO08MAR]Cow Travelling S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
P1460 [USACO2.1]健康的荷斯坦奶牛 Healthy Holsteins - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
AC代码部分:
int v,n,res=9999;
int a[300],food[200][300],ans[200];
int sel[300];
bool check(int x)
{
for(int i=1;i<=v;i++)
{
int sum=0;
for(int j=1;j<=x;j++)
sum+=food[sel[j]][i];
if(sum<a[i])return false;
}
return true;
}
void dfs(int x,int pos)
{
if(x>=res||pos>n+1)return;
if(check(x))
{
if(x<res)
{
res=x;
for(int i=1;i<=x;i++)ans[i]=sel[i];
}
return;
}
for(int i=pos;i<=n;i++)
{
sel[x+1]=i;
dfs(x+1,i+1);
}
}
分析:这个问题要求我们求出饲料所含维他命之和大于所需且饲料的种类最少、序号最小的结果,首先想到的是用dfs,但仅仅只用dfs一遍遍算到最深层总会有些不必要的麻烦,这时就需要用剪枝算法,当部分线路算到一半时,就已大于已经得出的结果,此时不必再继续向下深搜,及时止损,把此路直接删除,这样既方便又节约时间。
总结:剪枝,就是通过比较判断,避免一些不必要的遍历过程,形象的说,就是剪去了树中的某些树枝,故称剪枝。在查阅资料中学到,剪枝的精髓是在寻找起点的时候要有一个范围,如果这个起点到集合终止之间的元素已经不满足题目要求了,就没有必要搜索了。
2、连通块问题分析
例题:
P1506 拯救oibh总部 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
P1596 [USACO10OCT]Lake Counting S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
P1451 求细胞数量 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
AC代码:
#include<iostream>
using namespace std;
int ans;
int dr[4][2]={{-1,0},{1,0},{0,-1},{0,1}};
bool b[105][105];
void search(int x,int y)
{
b[x][y]=false;
for(int i=0;i<4;i++)
{
int nx=x+dr[i][0],ny=y+dr[i][1];
if(b[nx][ny])search(nx,ny);
}
}
int main()
{
int n,m;
char k;
cin>>m>>n;
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
cin>>k;
if(k!='0')b[i][j]=true;
}
}
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
if(b[i][j])
{
search(i,j);
ans++;
}
}
}
cout<<ans<<endl;
return 0;
}
分析:此题大致题意为(0为边界,求矩阵内非0数字所分块数(四个方位相连)的数量且最少),显而易见是个连通块问题,不过这个题只要求上下左右四个方向的连通,像Lake Counting S中,则需要八个方向的连通。输入完成之后把整个矩阵遍历一遍,遇到符合要求的就ans++,然后把它周围四个方块若为非零数则连通,直到遇到零为止。
判断两点是否连通、判断连通块个数等问题都是经常碰到的,使用dfs与并查集与之结合,这还需要多加练习。
3、最近新学习的知识点:字典序排序
P1460 [USACO2.1]健康的荷斯坦奶牛 Healthy Holsteins - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
此题输出要求:如果有多个解,输出饲料序号最小的,即字典序最小。
何为字典序,我来举一个“栗”子:
有1、2、3三个数字,对其进行排列 1 2 3 , 1 3 2 , 2 1 3 , 2 3 1 , 3 1 2 , 3 2 1,便是字典序。首先是第一个数字为最小的,其次排第二个数字,为次小的,之后继续向后排,当全排完后,再让次小的为第一个数字,继续安装上述规则排,这样的优点是输出结果为有序的且不重复。
找到了一个例题:Problem - 6468 (hdu.edu.cn)
模仿着正确代码写了写试试:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf=1<<30;
const int maxn=6007;
const double pi=acos(-1);
const int mod=1e9+7;
int ans=0,n,k;
int getnum(int n,int i){
int base=1,sum=0;
while(n>=(base*(i+1))){
sum+=base;
base*=10;
}
if(n>=(base*i))sum+=n-base*i+1;
return sum;
}
void getans(int &cnt,int cul){
if(++cnt==k){
ans=cul;
return ;
}
for(int i=0;i<=9;i++)
{
int t=cul*10+i;
if(t<=n) getans(cnt,t);
if(cnt>=k) return ;
}
}
int main(){
int T;
cin>>T;
while(T--){
cin>>n>>k;
int i;
for(i=1;i<=9;i++){
int num=getnum(n,i);
if(k>num) k-=num;
else break;
}
int cnt=0;
getans(cnt,i);
cout<<ans<<endl;
}
return 0;
}
不过最重要的还是自己能写出来,这必须学习更多的知识,解决更多的问题,学会总结归纳,并时常分析复习。