两种重要的思维方法,搜索方法O(∩_∩)O哈哈~
概念
DFS:深度优先搜索。可以用递归实现,也可以用栈实现。不一定是在图中,有很多DFS暴力搜索解决的问题。经常用来解决无法确定该写几层循环的问题。
要有一个可以被具体描述、存储的状态,状态可以作为递归函数的参数,如果难以传递后者传递的代价过高(比如是个数组),也可以设为全局变量,然后通过打可逆标记来改变状态。
对于每个状态,我们要知道它能达到的后续状态有哪些,通过递归来搜索每一个后续状态。
搜索完每一个状态之后,此时,我们需要清理搜索时打的标记,防止影响后续搜索。
当然也有结束的位置,我们要设置搜到某个位置就要返回,不能无限递归下去。
例题
1、全排列https://vjudge.net/contest/417427#problem/H
题目:
输入一个整数n(n <= 9),输出1、2、3、······、n这n个数的全排列(按照字典序输出)。
AC代码
#include<bits/stdc++.h>
using namespace std;
int n;
int vis[10];
vector<int> v(10);
//dfs到第k位
void dfs(int k){
for(int i=1;i<=n;i++){
if(vis[i]) continue;
vis[i] = 1;
v[k] = i;
if(k == n){
for(auto i : v) if(i) cout<<i<<' ';
cout<<'\n';
vis[i] = 0;
return;
}
else dfs(k+1);
vis[i] = 0;
}
}
int main(){
cin>>n;
dfs(1);
return 0;
}
2、https://codeforces.com/gym/338640/problem/A
题目大意:
给定n、k、s要求集合满足集合的每个元素<=n,元素个数为k,元素之和为s,问满足条件的集合有多少。
思路:
集合有无序性,从小到大DFS。我们可以DFS(当前元素个数m,当前元素和sum,当前元素最大值low)。当m=k,sum=s时,答案加1。
AC代码
#include <bits/stdc++.h>
using namespace std;
int n,k,s;
int ans;
void dfs(int m,int sum,int low){
if(m == k && sum == s){
ans++;
return;
}
for(int i=low;i<=n && i+sum <= s;i++){
dfs(m+1, sum+i, i+1);
}
}
int main() {
while(1){
ans=0;
cin>>n>>k>>s;
if(!n && !k && !s) break;
dfs(0,0,1);
cout<<ans<<endl;
}
return 0;
}
3、https://codeforces.com/gym/330157/problem/B
题目大意:
用火柴摆数字,约定数字的位数和使用的火柴数,问最大能摆出的数字。
思路:
数字从大到小DFS,当前这位确定之后要满足:剩余位数res*2<=剩余火柴数<=剩余位数res*7。
另解
第一次做的时候我想的十分复杂,通过计算用火柴摆每个数字的“性价比”,进行构造。
AC代码
#include<bits/stdc++.h>
using namespace std;
int a[10]={6,2,5,5,4,5,6,3,7,6};
void solve(){
int n,sum=0; string s;
cin>>n>>s;
for(int i=0;i<(int)s.length();i++){
sum+=a[s[i]-'0'];
}
int res=n;
for(int i=0;i<n;i++){
for(int j=9;j>=0;j--){
if((sum-a[j])>=(res-1)*2 && (sum-a[j])<=(res-1)*7){
cout<<j;
sum-=a[j];
res--;
break;
}
}
}
}
int main(){
int cas;
cin>>cas;
while(cas--){
solve();
cout<<'\n';
}
return 0;
}
另解
#include <bits/stdc++.h>
using namespace std;
//策略:将火柴摆成‘9’放前面
int a[10]={6,2,5,5,4,5,6,3,7,6};
int ans[10];
int main() {
int t;
cin>>t;
while(t--){
int n;
cin>>n;
string ss;
cin>>ss;
int sum=0;
for(int i=0;i<n;i++){
sum+=a[ss[i]-'0'];
}
int cc=6*n-sum;
memset(ans,0,sizeof(ans));
ans[9]=n;
if(cc<0){
ans[8]=abs(cc);
ans[9]-=abs(cc);
}
else if(cc>0){
ans[1]=cc/4;
ans[9]-=ans[1];
cc%=4;
if(cc==1){
if(ans[1]==0){
ans[9]--;
ans[5]++;
}
else if(ans[1]==1){
ans[1]--;
ans[9]--;
ans[7]=ans[4]=1;
}
else if(ans[1]>=2){
ans[9]--;
ans[1]-=2;
ans[7]=3;
}
}
else if(cc==2){
if(ans[1]==0){
ans[4]++;
ans[9]--;
}
else if(ans[1]>=1){
ans[7]=2;
ans[9]--;
ans[1]--;
}
}
else if(cc==3){
ans[9]--;
ans[7]=1;
}
}
for(int i=9;i>=0;i--){
for(int j=0;j<ans[i];j++){
cout<<i;
}
}
cout<<endl;
}
return 0;
}
4、https://codeforces.com/gym/341451/problem/E
题面:
题意:
有t个样例,每个样例包含一个n,问0~n有多少数字只由0,4,6,7,9组成。
思路:
dfs,如果当前位大于要求数字则往后的数位可以任意选取0,4,6,7,9,ans+=ppow[剩余位数];如果恰好等于要求的数字,则固定该位往后一位再重新做如上考虑。ppow[i]表示i位数有多少满足条件的数,显然,ppow[i]=ppow[i-1]*5.
AC代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll ppow[20],ans;
int dig[] = {0,4,6,7,9};
int num[20];
void dfs(int x, int ok){
if(!ok || !x){
ans += ppow[x];
return;
}
for(int i=0;i<5;i++){
if(num[x] == dig[i]){
dfs(x-1,1);
}
if(num[x] > dig[i]){
dfs(x-1,0);
}
}
}
void solve(){
ll n;
cin>>n;
int p=0;
while(n){
num[++p]=n%10;
n/=10;
}
dfs(p,1);
}
signed main(){
int cas;
cin>>cas;
ppow[0]=1;
for(int i=1;i<=19;i++) ppow[i]=ppow[i-1]*5;
while(cas--){
ans=0;
solve();
cout<<ans<<endl;
}
return 0;
}