网易今年把内推笔试放在牛客网上办,然后出了一批编程题。
题目在:
http://www.nowcoder.com/test/2252286/summary
http://www.nowcoder.com/test/2252291/summary
一共18个,好多(不同岗位抽3个不同的题的样子)……
慢慢写吧,做一题写一题。
以下题解将假定读者有下列知识,对下面所列举的细节不再赘述。
(如果有必要对此进行教学的,请站内信我)
C/C++的基本语法
同余的基本性质
(如果觉得一些细节还要讲的更具体的,也欢迎站内信)
本文地址:http://blog.youkuaiyun.com/fcxxzux/article/details/52138964
饥饿的小易
网址:http://www.nowcoder.com/questionTerminal/5ee8df898312465a95553d82ad8898c3
完整的思考思路:
懒人请跳过前2份代码和讲解,谢谢!
考虑正向枚举,直觉上,直接放弃——对无解的情况,你得每一步都考虑展开,每一步展开就2个分支,10万步最多2^100000个分支,怎么可能算完……
(我们回头看看这个做法)
那就考虑逆向枚举。
但是还有一个问题:位置的下标还是指数级增长的(x->x*4+3或x*8+7)
注意到,贝壳总生长在能被1,000,000,007(之后写作1e9+7)整除的位置。
利用这一点,考虑同余的性质,把下标用%1e9+7后的结果表示(因为我根本不在乎,最后具体数值,我只在意,下标是不是1e9+7的倍数)。
然后来倒着做:
从0(下标是1e9+7的情况)开始倒着推算,正着计算是先乘再加,倒着就要先减再除。
——减成了负数怎么办?
——加上1e9+7,变成等价正数(或者说,计算出其加法逆元)
——不能整除怎么办?就说明不可往后推吗?
——我上来就踩了这个坑……
除法并没有同余的性质,但是我们还有乘法逆元。
/4,在%1e9+7的意义下,等价于乘(1e9+8)/4=(2.5e8+2)
/8,在%1e9+7的意义下,等价于乘(1e9+8)/8=(1.25e8+1)
——然后我们愉快的用乘法替代除法吧!
ok,这样倒推最多100000步,记录每个数有没有被访问过,访问过就不要重复展开(像BFS一样),得到一个表,记录了每个数变为0要多少步
——试验运行一下,发现好像这部分的计算飞快啊!
之后管他来什么数,直接查表,表里没有,就是100000步达不到的,否则100000步可达,输出结果。
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <math.h>
#include <algorithm>
#include <map>
#include <queue>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
map<int,int> dis;
int main(){
dis[0]=0;
queue<int> q;
q.push(0);
while(!q.empty()){
int x=q.front();q.pop();
int y1=(x-7+mod)%mod;
y1=((long long)(125000001LL)*y1)%mod;
if(!dis[y1]){
dis[y1]=dis[x]+1;
if(dis[y1]<100000){
q.push(y1);
}
}
int y2=(x-3+mod)%mod;
y2=((long long)(250000002LL)*y2)%mod;
if(!dis[y2]){
dis[y2]=dis[x]+1;
if(dis[y2]<100000){
q.push(y2);
}
}
}
int n;
while(~scanf("%d",&n)){
printf("%d\n",dis[n]?dis[n]:-1);
}
return 0;
}
这样能通过了。
但是回头思考一下,不对啊!
这么做,最坏情况下也得展开2^100000个点啊,就算倒推角度,明确缩小了范围到1e9+7以内,但是1e9+7仍然很多!
那么正着做呢?
于是勇敢的写一发,直接提交,也通过了!
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <math.h>
#include <algorithm>
#include <map>
#include <queue>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
map<int,int> dis;
int main(){
int n;
while(~scanf("%d",&n)){
dis[n]=0;
queue<int> q;
q.push(n);
while(!q.empty()){
int x=q.front();q.pop();
int y1=(x*8LL+7)%mod;
if(!dis[y1]){
dis[y1]=dis[x]+1;
if(dis[y1]<100000){
q.push(y1);
}
}
int y2=(x*4LL+3)%mod;
if(!dis[y2]){
dis[y2]=dis[x]+1;
if(dis[y2]<100000){
q.push(y2);
}
}
}
printf("%d\n",dis[0]?dis[0]:-1);
}
return 0;
}
但是上面2种做法,都很慢,用时显示都有300ms以上了。
继续思考:
既然正着做,去重,不重复展开就能过,那就说明重复非常非常多(你可以试试看在倒着做的代码里,输出有多少能达到的,只有30万个),但是为什么有那么多?
观察变换形式,并做变形:
4x+3=4(x+1)-1
8x+7=8(x+1)-1
如果多层嵌套呢?
y=4x+3
8y+7=8((4(x+1)-1)+1)-1=8(4(x+1))-1=32(x+1)-1
如果你多枚举一些,就会发现,能变换出的数的形式都是:
a(x+1)-1,其中a是2的>=2的幂次数(4、8、16、32、64、……)
我们能否利用这个特点呢?
当然能!
考虑直接枚举那个a,从2^2一直到……等等,最大是2的多少次?
答:直接考虑最大情况,每次变换都选择8x+7那种,也就是,每次a乘上8,也就是说,最坏是(2^3)^100000=2^300000次
所以,枚举a,从2^2次