那么在dfs剪枝优化之前,我们还需知道回溯的原理。
(因为忘记提了)什么时候要回溯呢?
外部搜索:改变整个大状态,需要撤销上一步对量的改变。
内部搜索:在同一个状态内改变,比如搜路径。
剪枝操作有什么呢?
删除冗余,极大减少时间复杂度。
1、优化搜索顺序
在大部分情况下,我们应该优先搜索分支较小的节点。
什么意思呢?在构造递归搜索树的时候,要尽心类似贪心的思维,比如说在讨论背包问题时(比如接下来的小猫爬山),如果我从小到大或者任意放置,会导致接下来的分支剧增,如果从最大开始,很多的书都会放不进去,保证了局部最优,有利于求解。
2、排除等效冗余
最经典的问题就是当是一个组合问题的时候,不要用排列问题求解,比如放书顺序1 2 3 和 3 2 1对于放满状态并无影响。
3、可行性剪枝
变量不合法。如果某位变量要越界。比如数位dp的位数越界的话要return
4、最优性剪枝
如果这个已经不会更优,比如要求背包里放满时书最少,放两本书已经满了和放两本书还不满,那么放两本书要么最后状态不合法,要么会放三本书以上已经不是最优解。
5、记忆化搜索(dp!!!)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define PII pair<int, int>
#define PLL pair<ll, ll>
const int N = 1e6 + 10;
const int M = 2 * N;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const double PI = acos(-1.0);
const double eps = 1e-8;
int c[20];
bool st[20];
int n,w;
int ans = 18;
int sum[20];
//确定搜索方式:把猫放进框里
void dfs(int u,int cnt,int tl)//现在是哪个数,用了多少个数了,一共有tl组
{
if(tl >= ans) return;
if(cnt == n)
{
ans = tl;
return;
}
for(int i = 0; i < tl; i ++)
{
if(sum[i] + c[u] <= w)
{
sum[i] += c[u];
dfs(u + 1,cnt + 1,tl);
sum[i] -= c[u];//恢复现场
}
}
sum[tl] += c[u];
dfs(u + 1,cnt + 1,tl + 1);
sum[tl] -= c[u];//恢复现场
}
void solve()
{
cin >> n >> w;
for(int i = 0; i < n; i ++) cin >> c[i];
sort(c, c + n,[&](int a,int b){
return a > b;
});//优化搜索顺序
dfs(0,0,1);
cout << ans << endl;
}
int main()
{
solve();
system("pause");
return 0;
}
思路:
1、可填数的要求要满足Sr
Sl
S cell(x / 3,y / 3),即在这个3 * 3的格子内要有1~9个数,行1~9个数,列1~9个数;
2、对于 x = Sr
Sl
S cell(x / 3,y / 3)下的二进制数,进行lowbit的枚举优化,枚举到以后对row、lie、cell进行优化
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define PII pair<int, int>
#define PLL pair<ll, ll>
const int N = 9;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const double PI = acos(-1.0);
const double eps = 1e-8;
int ones[1 << N];//不用循环遍历这个数有多少个1,不然不用lowbit遍历不好找
int mp[1 << N];//map知道第几位是1 1 -> 1 10 -> 2 100 -> 3
int row[N],col[N],cell[3][3];
char str[100];
inline int lowbit(int x)
{
return x & -x;
}
//求可选方案的交集
inline int get(int x,int y)
{
return row[x] & col[y] & cell[x / 3][y / 3];
}
bool dfs(int cnt)
{
if(!cnt) return true;
//第一步 找到可选方案数最少的格子
int minn = 10;
int x,y;
for(int i = 0; i < N; i ++)
for(int j = 0; j < N; j ++)
{
if(str[i * 9 + j] == '.')
{
int t = ones[get(i,j)];
if(t < minn)
{
minn = t;
x = i, y = j;
}
}
}
for(int i = get(x,y); i; i -= lowbit(i))
{
int t = mp[lowbit(i)];
//修改状态
row[x] -= 1 << t;
col[y] -= 1 << t;
cell[x / 3][y / 3] -= 1 << t;
str[x * 9 + y] = '1' + t;
if(dfs(cnt - 1)) return true;
//恢复现场
row[x] += 1 << t;
col[y] += 1 << t;
cell[x / 3][y / 3] += 1 << t;
str[x * 9 + y] = '.';
}
return false;
}
void init()
{
for(int i = 0; i < N; i ++) row[i] = col[i] = (1 << N) - 1;// 2进制里边9个1,表示最开始情况下全能放
for(int i = 0; i < 3; i ++)
for(int j = 0; j < 3; j ++)
cell[i][j] = (1 << N) - 1;
}
void solve()
{
for(int i = 0 ; i < N; i ++) mp[1 << i] = i;
for(int i = 0; i < 1 << N; i ++)
{
int s = 0;
for(int j = i; j; j-= lowbit(j)) s ++;
ones[i] = s;//i的二进制表示种有s个1
}
while(cin >> str,str[0] != 'e')
{
init();
//去掉string上的点(初始化全能选),去掉row col cell
int cnt = 0;
for(int i = 0,k = 0; i < N; i ++)
for(int j = 0; j < N; j ++,k ++)
{
if(str[k] != '.')
{
int t = str[k] - '1';//把1 ~ 9映射成0 ~ 8
row[i] -= 1 << t;
col[j] -= 1 << t;
cell[i / 3][j / 3] -= 1 << t;
}
else cnt ++;
}
dfs(cnt);//把cnt个没有填过的格子填满
cout << str << endl;
}
}
int main()
{
solve();
system("pause");
return 0;
}
思路:
剪枝1:对于所有木棍的总和sum来说,只枚举sum的因数,因为总木棍数一定是一个整数
剪枝2:优化搜索顺序:从大到小枚举(优先枚举分支较小)
剪枝3:排除等效冗余方法:
1、用组合方式枚举而不是排列方式枚举
2、如果是木棒的第一根失败了,一定失败。因为小段放下去以后不成立,那么以后的都不会成立(因为如果合适的话,在同一根木棒里是等价的,等长木棒交换完成后在第一行也是等价的)即(!s == 0)第一次木棍啥也没找到,寄
3、如果当前木棒放下以后失败了,则略过所有长度相同的木棒
4、如果某一根木棍放到最后失败了就一定失败(s + w[i] == length[i]) //这种情况按理说是可以的,但是在我搜的过程中没出现,说明失败了
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define PII pair<int, int>
#define PLL pair<ll, ll>
const int N = 70;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const double PI = acos(-1.0);
const double eps = 1e-8;
int n;
int w[N],sum,length;
bool st[N];
bool dfs(int u,int s,int start)
{
if(u * length == sum) return true;
if(s == length) return dfs(u + 1,0,0);
//剪枝3-1:i从start开始枚举避免排列
for(int i = start; i < n; i ++)
{
if(st[i]) continue;
if(s + w[i] > length) continue;
st[i] = true;
if(dfs(u ,s + w[i],i + 1)) return true;
st[i] = false;
//如果走到这里,说明上述情况已经失败了
//对于失败的情况,我们进行剪枝
//剪枝3-2:如果放第一根的时候失败了,那么以后都不用放了
if(!s) return false;
//剪枝3-4:如果放最后一根的时候失败了,那么以后都不用放了
if(s + w[i] == length) return false;
int j = i;
while(j < n && w[j] == w[i]) j ++;
i = j - 1;
}
return false;
}
void solve()
{
while(cin >> n,n)
{
memset(st,0,sizeof st);
sum = 0;
for(int i = 0; i < n; i ++)
{
cin >> w[i];
sum += w[i];
}
//优化搜索顺序
sort(w,w + n);
reverse(w,w + n);
length = 1;
while(true)
{
if(sum % length == 0 && dfs(0,0,0))
{
cout << length << endl;
break;
}
length ++;
}
}
}
int main()
{
solve();
system("pause");
return 0;
}
思路:
剪枝1:从底向上搜,从大到小来枚举R,H,并且优先枚举R,因为R作用于体积是平方倍
剪枝2:当前层的R的范围是
。
最小值是因为从顶往下1~m按照递减的顺序递减下去以后,最小的结果是1--2--3--4--5----u,否则不会达到M层。最大值是上一层已经固定的值 - 1,同时由于体积的差值为
则一定满足
同理可得当前层高度H的范围是
剪枝3:预处理最小值minv,mins
剪枝4:S1 + S2 + S3 ······ + Su =
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define PII pair<int, int>
#define PLL pair<ll, ll>
const int N = 25;
const int M = 2 * N;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const double PI = acos(-1.0);
const double eps = 1e-8;
int n,m;
int minv[N],mins[N];
int R[N],H[N];
int ans = INF;
void dfs(int u,int v,int s)
{
if(v + minv[u] > n) return;
if(s + mins[u] >= ans) return;
if(s + 2 * (n - v) / R[u + 1] >= ans) return;
if(!u)
{
if(v == n) ans = s;
return;
}
for(int r = min(R[u + 1] - 1,(int)sqrt(n - v)); r >= u; r --)
{
for(int h = min(H[u + 1] - 1,( n - v) / r / r); h >= u; h --)
{
int t = 0;
if(u == m) t = r * r;
R[u] = r;
H[u] = h;
dfs(u - 1,v + r * r * h, s + 2 * r * h + t);
}
}
}
void solve()
{
cin >> n >> m;
for(int i = 1; i <= m; i ++)
{
minv[i] = minv[i - 1] + i * i * i;
mins[i] = mins[i - 1] + 2 * i * i;
}
R[m + 1] = H[ m + 1] = INF;
dfs(m,0,0);
if(ans == INF) puts("0");
else cout << ans << endl;
}
int main()
{
solve();
system("pause");
return 0;
}