亿点思考
地对于上一节的八数码问题,我们有了以下认知:实际上,广搜相当于可以记录从一个状态扩展到另一个状态的情况,很像数学上向量空间的概念。
搜索剪枝
核心:减少搜索树的大小
改变搜索顺序:如可以让起点与终点双向奔赴;
最优化剪枝:如当已经找到一个较优解的前提下,找到了一个比较优解更差的结果,那肯定在判断是较差解之后立刻终止寻找;
可行性剪枝:虽然还没到达终点,但已经知道终点去不了了,即提前做预判。
例1
N*M的迷宫中给定起点s和终点d,问是否能在t时刻恰好到达终点d。
恰好在t时刻,说明不能用广搜,因为恰好在t时刻说明有可能出现强行拖时间的情况,而广搜结果为最短路径。提前判断能否到达的方法:1、预判最短路径,沿着此路走到终点,若超时则不能到达;2、若t的奇偶性和s与y之间的直线路径的奇偶性不同,那么可以确定无法到达。所以最简单的方法用最短路求s到t的距离,若与t的奇偶性不同则不行。
链接:登录—专业IT笔试面试备考平台_牛客网 来源:牛客网 ?
小木棍
乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过50。现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。
我的想法:二分答案+深搜,猜一个答案出来再逐一验证。但对于细节考虑的很不周到。
正解:1、从小到大枚举最小可能长度;2、按从大到小的顺序摆放木棍进行深搜;3、若第i根棍子不能拼成假设的长,则需要跳过所有长为i的棍子;4、替换第i根棍子的第一根木棒没用,要去追究前面棍子的责任;5*、一个位置若不能摆放长为i的木棍,则其他木棍的同一个位置一样不行(代码中没写,因为前面的优化足够了)。
细节问题:别忘了还可以通过判断是否可以整除小木棍长度来剪枝!
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#include<vector>
#include<map>
using namespace std;
typedef long long ll;
const int maxn=100,inf=0x3f3f3f3f;
int n,sum,minn=inf,a[maxn],v[maxn];
bool cmp(int a,int b){
return a>b;
}
int dfs(int num,int len,int res,int last){
if(num==0&&res==0) return 1;//num=0 res!=0?
if(res==0) res=len, last=-1;
for(int i=last+1;i<n;++i){
if(v[i]||a[i]>res) continue;
v[i]=1;
if(dfs(num-1,len,res-a[i],i)) return 1;
v[i]=0;
if(res==a[i]||len==res) break;
while(a[i]==a[i+1]) i++;//指针移向i+1
}
return 0;
}
int main(){
cin>>n;
for(int i=0;i<n;++i){
cin>>a[i];
sum+=a[i];
minn=min(minn,a[i]);
}
sort(a,a+n,cmp);
for(int i=minn;;++i){
if(sum%i!=0) continue;
if(dfs(n,i,i,-1)){
cout<<i;
return 0;
}
}
return 0;
}
链接:登录—专业IT笔试面试备考平台_牛客网 来源:牛客网 ?
生日蛋糕
设从下往上数第i(1 ≤ i ≤ M)层蛋糕是半径为Ri,?高度为Hi的圆柱。当i<M时,要求Ri>Ri+1且Hi>Hi+1。
由于要在蛋糕上抹奶油,为尽可能节约经费,我们希望蛋糕外表面(最下一层的下底面除外)的面积Q最小。令Q= Sπ,请编程对给出的N和M,找出蛋糕的制作方案(适当的Ri和Hi的值),使S最小。
1、搜索方向:当前已有表面积加上之后层的预估最小表面积,若大于最优解则减掉;2、当前已有体积加上之后层的预估最小体积,若大于总体积则减掉;3、利用体积与面积之间的关系(目标体积-已有体积)/r *2+已有表面积>=ans。详细题解:生日蛋糕题目题解_牛客竞赛OJ
细节问题:注意当剪枝continue与while配合食用时时,计数器一定要在一开始就进入工作状态!QAQ……
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#include<map>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
const int N=1000000;
const int M = (int)1e2;
const int mod = 99991;
const int inf = 0x3f3f3f3f;
int n, m;
int ans = inf;
int r[25], h[25];
int min_v_sum[25];
int min_s_sum[25];
void dfs(int deep, int s, int v) {
if(!deep) {//若没有剩余层数 了
if(v == n)//若体积等
ans = min(ans, s);//记录答案
return;
}
r[deep] = min((int)sqrt(n - v), r[deep + 1] - 1) + 1;//r的最大值
while(r[deep] > deep) {//当r大于等于最小值时
--r[deep];
h[deep] = min((int)(double)(n - v) / r[deep] / r[deep], h[deep + 1] - 1) + 1;
while(h[deep] > deep) {//h大于最小值
//注意由于这里有多重剪枝会continue,所以计数器一定要在一开始就进入工作状态!
--h[deep];
if(v + min_v_sum[deep] > n) continue;
if(s + min_s_sum[deep] > ans) continue;
if(s + 2.0 * (n - v) / r[deep] > ans) continue;
if(deep == m) s = r[deep] * r[deep];
dfs(deep - 1, s + 2 * r[deep] * h[deep], v + r[deep] * r[deep] * h[deep]);
if(deep == m) s = 0;
}
}
}
int main() {
scanf("%d %d", &n, &m);
for(int i = 1; i <= m; ++i) {
min_s_sum[i] = min_s_sum[i - 1] + 2 * i * i;//前缀和求面积
min_v_sum[i] = min_v_sum[i - 1] + i * i * i;//前缀和求体积
}
h[m + 1] = r[m + 1] = inf;
dfs(m, 0, 0);
if(ans == inf) ans = 0;
printf("%d\n", ans);
return 0;
}
链接:登录—专业IT笔试面试备考平台_牛客网 来源:牛客网 ?
maze
小明来到一个由n x m个格子组成的迷宫,有些格子是陷阱,用'#'表示,小明进入陷阱就会死亡,'.'表示没有陷阱。小明所在的位置用'S'表示,目的地用'T'表示。
小明只能向上下左右相邻的格子移动,每移动一次花费1秒。有q个单向传送阵,每个传送阵各有一个入口和一个出口,入口和出口都在迷宫的格子里,当走到或被传送到一个有传送阵入口的格子时,小明可以选择是否开启传送阵。如果开启传送阵,小明就会被传送到出口对应的格子里,这个过程会花费3秒;如果不开启传送阵,将不会发生任何事情,小明可以继续向上下左右四个方向移动。
最短路径,肯定先想到广搜,那传送是不是可以作为其中的一份子去更新步数呢?其实不可以。如果一个位置为传送出口,通过传送入口到达需要4秒,而不用传送阵到达则需要3秒,但由于传送已经先发生了,占据了出口的坐标点,所以走过去时已经不能入队了,然后就违背了最短路原则QAQ。解决方法是,利用优先队列对入队的点进行维护,每次只保证到达时间最早的先出队,再标记已经经过的点。
细节问题:注意题目所给下标的起点从0开始!!!
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#include<map>
#include<vector>
//#include<cmath>
using namespace std;
typedef long long ll;
const int maxn=1005;
const int M = (int)1e2, mod = 99991, inf = 0x3f3f3f3f;
struct nd{
int x,y,t;
bool operator <(const nd &a)const{
return a.t<t;
}
};
int n,m,qs,sx,sy,tx,ty,x1,x2,y1,y2,et[maxn][maxn],vis[maxn][maxn];
int d[4][2]={0,1,0,-1,1,0,-1,0};
char mp[maxn][maxn];
priority_queue<nd>q;
int bfs(){
while(!q.empty()) q.pop();
q.push((nd){
sx,sy,0
});
while(!q.empty()){
nd tmp=q.top(); q.pop();
int nx=tmp.x, ny=tmp.y, nt=tmp.t;
if(nx==tx&&ny==ty) return nt;
if(vis[nx][ny]||mp[nx][ny]=='#') continue;
vis[nx][ny]=1;
if(et[nx][ny]){
int xx=et[nx][ny]/1000, yy=et[nx][ny]%1000;
q.push((nd){
xx,yy,tmp.t+3
});
}
for(int i=0;i<4;++i){
int cx=nx+d[i][0],cy=ny+d[i][1];
if(cx<0||cy<0||cx>=n||cy>=m||vis[cx][cy]||mp[cx][cy]=='#')
continue;
int ct=tmp.t+1;
if(cx==tx&&cy==ty) return ct;
q.push((nd){
cx,cy,ct
});
}
}
return -1;
}
int main(){
while(scanf("%d%d%d",&n,&m,&qs)==3){
memset(vis,0,sizeof(vis));
memset(et,0,sizeof(et));
for(int i=0;i<n;++i){
getchar();
for(int j=0;j<m;++j){
scanf("%c",&mp[i][j]);
if(mp[i][j]=='S') sx=i,sy=j;
else if(mp[i][j]=='T') tx=i,ty=j;
}
}
for(int i=1;i<=qs;++i){
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
et[x1][y1]=x2*1000+y2;
}
printf("%d\n",bfs());
}
return 0;
}
竞赛题库_ACM/NOI/CSP基础提高训练专区_牛客竞赛OJ
Ocean Currents
对于大片水域的船来说,强流可能很危险,但经过仔细规划,可以利用它们来帮助船到达目的地。你的工作是帮助制定计划。在每个位置,电流都向某个方向流动。船长可以选择顺流而下,不使用能量,或者以一个能量单位为代价向任何其他方向移动一格。船总是在以下八个方向之一移动:北,南,东,西,东北,西北,东南,西南。船不能离开湖的边界。你要帮助他制定一个策略,以最少的能源消耗到达目的地。
利用双向队列,对距离起点距离较近的数直接存入队首。
细节问题:看了看01bfs和优先队列bfs的时间,前者510ms后者1000多ms,所以对于这种代价只有两种情况的广搜,需要尽量用双向队列实现。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#include<deque>//
#include<stack>
#include<cstring>
using namespace std;
const int inf = 0x3f3f3f, maxn=1005;
struct nd{
int x,y,e;
};
int r,c,n,sx,sy,tx,ty,vis[maxn][maxn];
int dx[10]={-1,-1, 0, 1, 1, 1, 0,-1};
int dy[10]={ 0, 1, 1, 1, 0,-1,-1,-1};
char mp[maxn][maxn];
deque<nd>q;
int bfs(){
q.push_back((nd){sx,sy,0});
while(!q.empty()){
nd tmp=q.front(); q.pop_front();
int nx=tmp.x,ny=tmp.y,ne=tmp.e;
// cout<<"x: "<<nx<<" y: "<<ny<<" e: "<<ne<<endl;
if(vis[nx][ny]) continue;
if(nx==tx&&ny==ty) return ne;
vis[nx][ny]=1;
for(int i=0;i<8;++i){
int cx=nx+dx[i],cy=ny+dy[i],ce;
if(cx<1||cy<1||cx>r||cy>c) continue;
int d=mp[nx][ny]-'0',f=1;
if(dx[d]==dx[i]&&dy[d]==dy[i]) f=0;
ce=ne+f;
if(f) q.push_back((nd){cx,cy,ce});
else q.push_front((nd){cx,cy,ce});
}
}
}
int main(){
scanf("%d%d",&r,&c);
for(int i=1;i<=r;++i){
getchar();
for(int j=1;j<=c;++j)
scanf("%c",&mp[i][j]);
}
scanf("%d",&n);
for(int i=0;i<n;++i){
scanf("%d%d%d%d",&sx,&sy,&tx,&ty);
memset(vis,0,sizeof(vis));
while(!q.empty()) q.pop_back();
printf("%d\n",bfs());
}
return 0;
}
Problem - 590C - Codeforces
Three States
由于道路总是很昂贵,新成立的联盟的州政府要求你帮助他们评估成本。为此,您获得了一张地图,该地图可以表示为由 n 行和 m 列组成的矩形表。地图的任何单元格要么属于三个州之一,要么是允许修建道路的区域,要么是不允许修建道路的区域。如果一个单元格属于其中一种状态,或者道路建在该单元格中,则该单元格称为可通行。如果与移动对应的单元格存在并且可以通过,则您可以从任何可通过的单元格向上、向下、向右和向左移动。
你的任务是在最少数量的单元格内建造一条道路,以便仅使用可通行的单元格就可以从任何状态的任何单元格到达任何其他状态的任何单元格。
按照我一开始的想法,是通过无脑广搜+优先队列进行遍历,遇到点则步数加一,遇到国家不加步数。但这个算法有个致命的错误:每次走完都要标记点,这说明当遇到国家后,虽然是能给标记,但也没办法回到原来的位置了!如下方地图的部分情况,当从最右上角走到1,此时会直接标记
1 .????????这个点,那么从1出发就只能走下方了……最简便的方法是算出三个国家到地图上任意一点
. .????????的位置,然后求得三个国家到达一点中最小总步数即为答案。01bfs则是保证了每次到达一个点一定是用最小步数做到的。
细节问题:如果三个国家同时跳到了‘.’上,而且各自都算了一步,就应该减去多加的2。
详细题解:CF590C Three States(01 BFS)_ylxmf2005's Blog-CSDN博客
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#include<deque>//
#include<stack>
#include<cstring>
using namespace std;
const int inf = 0x3f3f3f, maxn=1005;
int dx[]={0,0,1,-1};
int dy[]={1,-1,0,0};
int vis[maxn][maxn],r,c,disa[maxn][maxn],disb[maxn][maxn],disc[maxn][maxn];
char mp[maxn][maxn];
struct nd{
int x,y;
};
deque<nd>q;
void bfs(int guo,int dis[][maxn]){
for(int i=1;i<=r;++i){
for(int j=1;j<=c;++j){
if(mp[i][j]==guo+'0'){
dis[i][j]=0;
q.push_back((nd){
i,j
});
}
}
}
while(!q.empty()){
nd tmp=q.front(); q.pop_front();
int nx=tmp.x, ny=tmp.y;
for(int i=0;i<4;++i){
int cx=nx+dx[i], cy=ny+dy[i], cost=1;
if(cx<1||cy<1||cx>r||cy>c||mp[cx][cy]=='#'||dis[cx][cy]!=inf)
continue;
if(mp[cx][cy]!='.') cost=0;
dis[cx][cy]=dis[nx][ny]+cost;
if(cost) q.push_back((nd){
cx,cy
});
else q.push_front((nd){
cx,cy
});
}
}
}
int main(){
scanf("%d%d",&r,&c);
for(int i=1;i<=r;++i){
getchar();
for(int j=1;j<=c;++j){
scanf("%c",&mp[i][j]);
disa[i][j]=disb[i][j]=disc[i][j]=inf;
}
}
bfs(1,disa); bfs(2,disb); bfs(3,disc);
int ans=inf;
for(int i=1;i<=r;++i){
for(int j=1;j<=c;++j){
int cost=disa[i][j]+disb[i][j]+disc[i][j];
if(mp[i][j]=='.') cost-=2;
ans=min(ans, cost);
}
}
if(ans==inf) printf("-1");
else printf("%d",ans);
return 0;
}
链接:登录—专业IT笔试面试备考平台_牛客网 来源:牛客网 ?
魔法数字
牛妹给牛牛写了一个数字n,然后又给自己写了一个数字m,她希望牛牛能执行最少的操作将他的数字转化成自己的。 操作共有三种,如下: ? ? ? ? 1.在当前数字的基础上加一,如:4转化为5 ? ? ? ? 2.在当前数字的基础上减一,如:4转化为3 ? ? ? ? 3.将当前数字变成它的平方,如:4转化为16 返回最少需要的操作数。
普通的广搜,但注意n能扩充的最大数值为2*m-n,因为再大就不如一点点加过去了,这一点作为剪枝的依据。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
const int inf = 0x3f3f3f, maxn=3005;
int n,m,vis[maxn];
struct nd{
int x,stp;
};
queue<nd>q;
int bfs(){
q.push((nd){
n,0
});
while(!q.empty()){
nd cur=q.front(); q.pop();
for(int i=0;i<3;++i){
int tmp=cur.x, sp=cur.stp;
if(i==0) tmp+=1;
else if(i==1) tmp-=1;
else tmp*=tmp;
if(tmp>2*m+cur.x||vis[tmp]) continue;
vis[tmp]=1;
if(tmp==m) return sp+1;
q.push((nd){
tmp, sp+1
});
}
}
}
int main(){
char c;
cin>>n>>c>>m;
printf("%d",bfs());
return 0;
}
链接:登录—专业IT笔试面试备考平台_牛客网 来源:牛客网 ?
新集合
集合 s 中有整数 1?到 n,牛牛想从中挑几个整数组成一个新的集合。
现在牛妹给牛牛加了 m?个限制 ,每个限制包含两个整数??,且 u 和 v 不能同时出现在新集合中 。请问牛牛能组成的新集合多少种。可以选 0 个数。返回一个整数,即新集合的种类数。
这道题其实就是选和不选的问题,只要从第一个数一直向下找就行啦,根本不需要把每一个集合都看一遍的,只要最后看看随机生成的这个集合符不符合条件即可……
/**
* struct Point {
* int x;
* int y;
* Point(int xx, int yy) : x(xx), y(yy) {}
* };
*/
class Solution {
public:
int ans=0,n,m,vis[30];
vector<Point>limit;
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param n int 集合大小
* @param m int 限制数量
* @param limit Pointvector 不能同时出现的(u,v)集合
* @return int
*/
void dfs(int dep){
if(dep==n+1){
for(int i=0;i<m;++i){
int x=limit[i].x, y=limit[i].y;
if(vis[x]&&vis[y]) return ;
}
ans++;
return ;
}
vis[dep]=1;
dfs(dep+1);
vis[dep]=0;
dfs(dep+1);
}
int solve(int n, int m, vector<Point>& limit) {
// write code here
this->n=n; this->m=m; this->limit=limit;
memset(vis,0,sizeof(vis));
dfs(1);
return ans;
}
};
|