各种各样的搜索(⊙ ▽ ⊙)(3)剪枝之章
作者:互联网
各种各样的搜索(⊙ ▽ ⊙)(3)剪枝之章
剪枝主要指搜索过程的优化,其实很多内容都是之前我们写一些简单题的时候写过的,但这里很难想到用这些方法来优化,我们通过一些例题来提供一些优化的思路,这种类型的问题确实需要我们平时的积累。
一、小木棍 [数据加强版]
题目描述
乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过5050。
现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。
给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。
输入格式
共二行。
第一行为一个单独的整数N表示砍过以后的小木棍的总数,其中N≤65
(管理员注:要把超过5050的长度自觉过滤掉,坑了很多人了!)
第二行为N个用空个隔开的正整数,表示N根小木棍的长度。
输出格式
一个数,表示要求的原始木棍的最小可能长度
输入输出样例
输入 #1
9
5 2 1 5 2 1 5 2 1
输出 #1
6
这道题我们如果采取暴力的话,一定还是能找到一些思路的,但显然自己都会嫌麻烦,都会感觉不可能过,所以我们就在暴力搜索的基础上来优化
AC code
#include <bits/stdc++.h>
#pragma GCC optimize(3,"Ofast","inline")
#define re register
#define ll long long
#define ull unsigned long long
#define r read()
using namespace std;
//速读
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
//并查集
int f[1];
int found(int k){
if(f[k] == k){
return k;
}
return f[k] = found(f[k]);
}
//辗转相除法
int gcd(int p,int q){
int t = p % q;
return t==0?q:gcd(q,t);
}
//阶乘
int fac(int k){
int ans = 1;
for(int i = 1; i<= k; i++){
ans *= i;
}
return ans;
}
//每个有效段的长度
int nums[70];
//有效段个数
int cnt = 0;
//长度总和
int sum = 0;
//指向下一个长度小于该棍子的棍子
int nex[70];
//在确定长度后,更新根数
int pass;
int len;
//检测是否可以通过
bool checkPass = 0;
//检测是否被访问过
bool vis[70];
//最小长度
bool cmp(int a, int b){
return a > b;
}
// k 已经排好的木棍数
// last 目前查找的木棍的上一个木棍的编号
// rest 该木棍还未拼的长度
void dfs(int index, int last, int rest){
// cout<<"rest:"<<rest<<endl;
int i;
//如果这根木棍拼完了
if(rest == 0){
//如果是最后一根
if(index == pass){
checkPass = 1;
return;
}
//再找一个很长的没有用过的木棍继续拼接
for(i = 1; i <= cnt; i++){
if(vis[i] == 0){
break;
}
}
vis[i] = 1;
dfs(index+1,i,len - nums[i]);
vis[i] = 0;
//一旦成功不管到哪一步都直接退出
if(checkPass){
return;
}
}
//优化:二分来查找下一个适合插入的木棍
int le = last + 1;
int ri = cnt;
int mid;
while(le < ri){
mid = (le + ri) >> 1;
if(nums[mid] <= rest){
ri = mid;
}
else{
le = mid + 1;
}
}
for(i = le; i <= cnt; i++){
if(!vis[i]){
vis[i] = 1;
dfs(index,i,rest - nums[i]);
vis[i] = 0;
if(checkPass){
return;
}
//如果走到这里说明目前拼不成
//优化:因为我们一开始做了排序所以这两种情况我们可以分析一下
//1、剩余的部分 == 目前棍子的长度:此时把这根棍子拼上去已经是最优情况,却依然无法成功,所以可以直接放弃
//2、剩余部分 == 原始长度:此时一点都没拼,把这根棍子拼进去显然是必然情况,如果失败的话说明前面拼错了,直接放弃
if(rest == nums[i] || rest == len){
return;
}
i = nex[i];
if(i == cnt){
return;
}
}
}
}
int main()
{
ios::sync_with_stdio(false);
int n = r;
for(int i = 1; i <= n; i++){
int temp = r;
if(temp <= 50){
cnt++;
nums[cnt] = temp;
sum += temp;
}
}
//优化:排序,从大拿到小,越来越灵活
sort(nums + 1, nums + cnt + 1,cmp);
//优化:预处理next数组,这样如果某个棍子不行,可以不再去尝试长度相同的棍子
nex[cnt] = cnt;
for(int i = cnt - 1; i > 0; i--){
if(nums[i] == nums[i+1]){
nex[i] = nex[i+1];
}
else{
nex[i] = i;
}
}
//优化:只需要判断最长的棍子的长度到总长度一半的长度
for(len = nums[1]; len <= sum / 2; len++){
//优化:如果不能拼出整根就直接跳过
if(sum % len != 0){
continue;
}
pass = sum / len;
checkPass = 0;
vis[1] = 1;
dfs(1,1,len - nums[1]);
vis[1] = 0;
if(checkPass){
cout<<len<<endl;
return 0;
}
}
//如果没法拆分那就是整个拼在一起
cout<<sum<<endl;
return 0;
}
二、[NOIP2009 提高组] 靶形数独
题目描述
小城和小华都是热爱数学的好学生,最近,他们不约而同地迷上了数独游戏,好胜的他们想用数独来一比高低。但普通的数独对他们来说都过于简单了,于是他们向 Z 博士请教,Z 博士拿出了他最近发明的“靶形数独”,作为这两个孩子比试的题目。
靶形数独的方格同普通数独一样,在 9 格宽且 9 格高的大九宫格中有 9 个 3 格宽且 3 格高的小九宫格(用粗黑色线隔开的)。在这个大九宫格中,有一些数字是已知的,根据这些数字,利用逻辑推理,在其他的空格上填入 1 到 9 的数字。每个数字在每个小九宫格内不能重复出现,每个数字在每行、每列也不能重复出现。但靶形数独有一点和普通数独不同,即每一个方格都有一个分值,而且如同一个靶子一样,离中心越近则分值越高。(如图)
上图具体的分值分布是:最里面一格(黄色区域)为 10 分,黄色区域外面的一圈(红色区域)每个格子为 9 分,再外面一圈(蓝色区域)每个格子为 8 分,蓝色区域外面一圈(棕色区域)每个格子为 7 分,最外面一圈(白色区域)每个格子为 6 分,如上图所示。比赛的要求是:每个人必须完成一个给定的数独(每个给定数独可能有不同的填法),而且要争取更高的总分数。而这个总分数即每个方格上的分值和完成这个数独时填在相应格上的数字的乘积的总和
总分数即每个方格上的分值和完成这个数独时填在相应格上的数字的乘积的总和。如图,在以下的这个已经填完数字的靶形数独游戏中,总分数为 2829。游戏规定,将以总分数的高低决出胜负。
由于求胜心切,小城找到了善于编程的你,让你帮他求出,对于给定的靶形数独,能够得到的最高分数。
输入格式
一共 9 行。每行 9 个整数(每个数都在 0∼9 的范围内),表示一个尚未填满的数独方格,未填的空格用“0”表示。每两个数字之间用一个空格隔开。
输出格式
输出共 1 行。输出可以得到的靶形数独的最高分数。如果这个数独无解,则输出整数 −1。
输入输出样例
输入 #1
7 0 0 9 0 0 0 0 1
1 0 0 0 0 5 9 0 0
0 0 0 2 0 0 0 8 0
0 0 5 0 2 0 0 0 3
0 0 0 0 0 0 6 4 8
4 1 3 0 0 0 0 0 0
0 0 7 0 0 2 0 9 0
2 0 1 0 6 0 8 0 4
0 8 0 5 0 4 0 1 2
输出 #1
2829
输入 #2
0 0 0 7 0 2 4 5 3
9 0 0 0 0 8 0 0 0
7 4 0 0 0 5 0 1 0
1 9 5 0 8 0 0 0 0
0 7 0 0 0 0 0 2 5
0 3 0 5 7 9 1 0 8
0 0 0 6 0 1 0 0 0
0 6 0 9 0 0 0 0 1
0 0 0 0 0 0 0 0 6
输出 #2
2852
说明/提示
数据规模与约定
- 对于 40% 的数据,数独中非 0 数的个数不少于 30;
- 对于 80% 的数据,数独中非 0 数的个数不少于 26;
- 对于 100% 的数据,数独中非 0 数的个数不少于 24。
这题也是一拿到我们就能看出来是搜索,但也能看出来非常复杂,这道题的剪枝其实还算好理解,其实就是找出0最少的那一行先开始试,这样就能大量减少要搜索的情况
AC code
#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
//#define re register
//#define ll long long
//#define ull unsigned long long
#define r read()
using namespace std;
//速读
inline int read();
并查集的查找
//int found(int k);
辗转相除法------返回最大公因数
//int gcd(int p,int q);
阶乘
//int fac(int k);
st表
//int st[10000][30];
//int lg[10000];
初始化
//void initST(int n);
查找
//int seekST(int le, int ri);
线性基
//ll p[101];
添加
//void add_key(ll x);
//快速幂
//ll ksm(ll a, ll b){
// ll c = 1;
// while(b){
// if(b % 2 == 1){
// c *= a;
// }
// a *= a;
// b >>= 1;
// }
// return c;
//}
//线性筛
//void xxs(){
// bool nums[n];
// for(int i = 2; i <= n; i++){
// if(!nums[i]){
// for(int j = i +i; j <= n; j+=i){
// nums[j] = 1;
// }
// }
// }
//}
typedef struct Row{
int h,cnt;
}row;
row ro[10];
bool cmp(row a, row b){
return a.cnt < b.cnt;
}
const int N = 10;
int ans[N][N],a[N][N],vis[3][N][N],b[82],maxn,flag;
int getG(int x, int y){
if(x>=1&&x<=3){
if(y>=1&&y<=3) return 1;
else if(y>=4&&y<=6) return 2;
else return 3;
}
if(x>=4&&x<=6){
if(y>=1&&y<=3) return 4;
else if(y>=4&&y<=6) return 5;
else return 6;
}
if(x>=7&&x<=9){
if(y>=1&&y<=3) return 7;
else if(y>=4&&y<=6) return 8;
else return 9;
}
}
int getS(int x, int y){
if(x == 1 || y == 1 || x == 9 || y == 9) return 6;
else if(x == 2 || y == 2 || x == 8 || y == 8) return 7;
else if(x == 3 || y == 3 || x == 7 || y == 7) return 8;
else if(x == 4 || y == 4 || x == 6 || y == 6) return 9;
else return 10;
}
int sum(){
int sum = 0;
for(int i = 1; i <= 9; i++){
for(int j = 1; j <= 9; j++){
sum += ans[i][j] * getS(i,j);
}
}
return sum;
}
void dfs(int t){
if(t == 82){
flag = 1;
maxn = max(maxn,sum());
return;
}
int x = b[t] / 9 + 1;
int y = b[t] % 9;
if(!y){
x = b[t] / 9;
y = 9;
}
if(!a[x][y]){
for(int i = 1; i <= 9; i++){
int g = getG(x,y);
if(!vis[0][x][i] && !vis[1][y][i] && !vis[2][g][i]){
ans[x][y] = i;
vis[0][x][i] = 1;
vis[1][y][i] = 1;
vis[2][g][i] = 1;
dfs(t + 1);
vis[0][x][i] = 0;
vis[1][y][i] = 0;
vis[2][g][i] = 0;
}
}
}
else{
dfs(t+1);
}
}
int k = 1;
int main()
{
ios::sync_with_stdio(false);
for(int i = 1; i <= 9; i++){
int cnt = 0;
for(int j = 1; j <= 9; j++){
cin >> a[i][j];
if(!a[i][j]){
cnt++;
}
else{
int v = a[i][j];
int g = getG(i,j);
ans[i][j] = v;
vis[0][i][v] = 1;
vis[1][j][v] = 1;
vis[2][g][v] = 1;
}
}
ro[i].h = i;
ro[i].cnt = cnt;
}
sort(ro+1,ro+10,cmp);
int num = 0;
for(int i = 1; i <= 9; i++){
for(int j = 1; j <= 9; j++){
int x = ro[i].h;
int y = j;
num++;
b[num] = (x-1) * 9 + y;
}
}
dfs(k);
if(flag){
cout<<maxn<<endl;
}
else{
cout<<-1<<endl;
}
return 0;
}
//速读
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
并查集
//int f[1];
//int found(int k){
// if(f[k] == k){
// return k;
// }
// return f[k] = found(f[k]);
//}
辗转相除法
//int gcd(int p,int q){
// int t = p % q;
// return t==0?q:gcd(q,t);
//}
阶乘
//int fac(int k){
// int ans = 1;
// for(int i = 1; i<= k; i++){
// ans *= i;
// }
// return ans;
//}
初始化st表
//void initST(int n){
// for(int i = 1; i <= n; i++){
// int temp = r;
// st[i + n][0] = st[i + n + n][0]= st[i][0] = temp;
// }
// for(int i = 2; i <= n * 3; i++){
// lg[i] = lg[i >> 1] + 1;
// }
// int ln = lg[n + n + n];
// for(int i = 1; i <= ln; i++){
// for(int j = 1; j + (1 << (i - 1)) - 1<= n * 3; j++){
// st[j][i] = max(st[j][i-1],st[j+(1 << (i - 1))][i-1]);
// }
// }
//}
查找st表
//int seekST(int le, int ri){
// int len = ri - le + 1;
// int q = lg[len];
// return max(st[le][q],st[ri - (1 << q) + 1][q]);
//}
添加到线性基
//void add_key(ll x){
// for(int i = 62; i >= 0; i--)
// {
// if(!(x >> (ll)i))
// continue;
// if(!p[i])
// {
// p[i] = x;
// break;
// }
// x ^= p[i];
// }
//}
标签:剪枝,cnt,之章,return,各种各样,nums,int,vis,ch 来源: https://blog.csdn.net/qq_51029409/article/details/115400517