前言
无论在什么地方,总有一类题目,我们很难想到正解,为了得分,我们不得不使用暴力或者搜索的方法骗分,异或正解就是暴力或者搜索。我们都知道,原始的搜索效率极低,很多情况下无法通过题目。
剪枝
我们也知道,搜索的过程相当于程序在遍历搜索树的过程,有些时候,我们可以提前知道搜索树上某些枝条上一定无解,此时,我们就没有必要浪费时间在这些枝条上进行搜索,相当于把它给剪去,这就是剪枝。
剪枝的三大原则:正确、准确、高效。其中,正确性是最最重要的,如果你把正解给剪掉了,一切白搭!
剪枝可以分为可行性剪枝和最优性剪枝。常见的方法有:控制上下界、排除无效解、记忆化、排除等效冗余等。
剪枝的副作用是让程序的时间复杂度变得无法计算。而剪枝的题目没有什么技巧,只有多想多刷。
例题
洛谷P2383
【题目】: 现给出一些木棒长度,那么能否用给出的木棒(木棒全用完)组成一个正方形呢?
【思路】: 本题有以下几个剪枝:
如果木棒长度总和
不是
的倍数,一定是输出no
把木棒分成四类,每一类放在一条边上,则如果某一类的木棒长度总和
,一定无解。
当然了,如果加上快读(读优)肯定可以更快哦!
【代码】:
int a[25],sum,test_number,n;
bool dfs(int k,int s1,int s2,int s3,int s4){
if (k>n){
if (s1==s2&&s2==s3&&s3==s4)
return true;
else return false;
}
if (s1+a[k]<=sum/4){//剪枝2:限制搜索条件
if (dfs(k+1,s1+a[k],s2,s3,s4))
return true;
}
if (s2+a[k]<=sum/4){
if (dfs(k+1,s1,s2+a[k],s3,s4))
return true;
}
if (s3+a[k]<=sum/4){
if (dfs(k+1,s1,s2,s3+a[k],s4))
return true;
}
if (s4+a[k]<=sum/4){
if (dfs(k+1,s1,s2,s3,s4+a[k]))
return true;
}
return false;
}
#define gc getchar()
#define g(c) isdigit(c)
inline int read(){
char c=0;int x=0;bool f=0;
while (!g(c)) f=c=='-',c=gc;
while (g(c)) x=x*10+c-48,c=gc;
return f?-x:x;
}
int main(){
test_number=read();
while (test_number--){
n=read();sum=0;
for(int i=1;i<=n;i++){
a[i]=read();
sum+=a[i];
}
if (sum%4)
printf("no\n");
else{
if (dfs(1,0,0,0,0))
printf("yes\n");
else printf("no\n");
}
}
return 0;
}
洛谷P1025
【题目】:
将整数
分成
份,且每份不能为空,任意两个方案不相同(不考虑顺序)。
例如:
,
,下面三种分法被认为是相同的。
问有多少种不同的分法。
【思路】: 因为解不考虑顺序,所以我们可以将划分出来的数从小到大排序搜索,即设
为解,则一定保证
。
因此有一个上下界剪枝,即设将
分解成
,则
。
当然,这样虽然可以AC
,但仍然可以优化,即加上记忆化。设
表示把
分为
分,每份数都
的方案数。
【代码】:
int f[210][10][210],n,k;
int dfs(int n,int k,int last){
if (n==0) return 1;
if (k==0) return 0;
if (f[n][k][last]!=-1)
return f[n][k][last];
f[n][k][last]=0;
for(int i=last;i<=n/k;i++)
f[n][k][last]+=dfs(n-i,k-1,i);
return f[n][k][last];
}
int main(){
scanf("%d%d",&n,&k);
memset(f,-1,sizeof(f));
printf("%d",dfs(n,k,1));
return 0;
}