御坂御坂题解(出自北航校赛) 约瑟夫环问题高效解决方案

御坂御坂

题目如下:

 

由提示可定义函数f(x):

f(1)=0

当x>1时有

f(x)=(f(x-1)+3)mod x

这个函数的意思是,f(x)是x个人的时候最后一个剩下的妹妹的编号

f(x+1)的时候是第0个人先出局了,这样子就只剩下了x个妹妹,然后第3个人就变成了f(x)的第一个出局的人,剩余人数也是x个人,那么把御坂妹妹的编号转换一下就是f(x+1)的游戏结果。

所以得出公式f(x)=(f(x-1)+3)mod x

关于这个公式我们打个表不难发现,这个公式是分段线性递增的,又因为mod每次+1而f(x)是每次+3,所以每隔x/2次增长后,f(x)会被mod减小一次,所以每经过x*3/2后,f(x)会被清零一次

就是说分段的时候x每次会增加x/2,也就是x=(3/2)*x

也就是说x是指数级递增的。

所以你可以求f是哪一段的,又因为是线性函数,所以可以直接求值。我们就能在log(n)的复杂度得到f(x)的值

然后这题的答案就是暴力跑m次f(x)来求答案,但是在log(m)次之内中一定会变成f(x)+1=f(f(x)+1)的情况,就可以直接跳出得到答案了

这题的做法

1、 O(log(n)*log(m))

log(n)的复杂度求f(n),log(m)次递归求解。

log(n)求f(n)的做法(1)

 1 #include<math.h>
 2 #include<stdio.h>
 3 #include<string.h>
 4 #include<stdlib.h>
 5 #include<algorithm>
 6 using namespace std;
 7 typedef long long ll;
 8 /*f[1]=0;
 9 f[n]=(f[n-1]+3)%n
10 c[n]=c[f[n]+1]*/
11 ll f(ll n)
12 {
13     ll ans=0,m=1;
14     while(m<n)
15     {
16         ll res=m-ans;
17         ll add=res/2+(res%2);
18         if(m+add>n)break;
19         m+=add;
20         ans=ans+add*3-m;
21     }
22     return (ans+(n-m)*3)%n;
23 }
24 ll c(ll n,ll m,ll pre)
25 {
26     if(pre==n)return pre;
27     if(m==0)return n;
28     return c(f(n)+1,m-1,n);
29 }
30 int main()
31 {
32     ll t;
33     scanf("%lld",&t);
34     while(t--)
35     {
36         ll n,m;
37         scanf("%lld%lld",&n,&m);
38         printf("%lld\n",c(n,m,n+1));
39     }
40     return 0;
41 }
View Code

log(n)求f(n)的做法(2)::这个方法的原理请参考:https://www.cnblogs.com/weidiao/p/5966204.html(这个效率比上面的低一些,因为是递归所以比较慢,(1)和(2)只不过一个是正着推一个是反着推)

 1 #include<math.h>
 2 #include<stdio.h>
 3 #include<string.h>
 4 #include<stdlib.h>
 5 #include<algorithm>
 6 using namespace std;
 7 typedef long long ll;
 8 /*f[1]=0;
 9 f[n]=(f[n-1]+3)%n
10 c[n]=c[f[n]+1]*/
11 ll f(ll n)
12 {
13     if(n==1)return 0;
14     if(n==2)return 1;
15     ll temp=f(n-n/3);
16     return (temp+((temp-n%3)<0?0:(temp-n%3))/2+n-n%3)%n;
17 }
18 ll c(ll n,ll m,ll pre)
19 {
20     if(pre==n)return pre;
21     if(m==0)return n;
22     return c(f(n)+1,m-1,n);
23 }
24 int main()
25 {
26     ll t;
27     scanf("%lld",&t);
28     while(t--)
29     {
30         ll n,m;
31         scanf("%lld%lld",&n,&m);
32         printf("%lld\n",c(n,m,n+1));
33     }
34     return 0;
35 }
View Code

 

2、O(log(log(n))*log(m)

对f(n)进行打表,预处理出来一个分段的表,然后在表上进行二分求f(n),这样子求f(n)就变成了log(log(n))的复杂度。所以时间复杂度为log(log(n))*log(m)

打表代码如下:

 1 void db()
 2 {
 3     ll x=1;
 4     ll f=0;
 5     while(x<=(ll)1e18)
 6     {
 7         printf("%lld %lld\n",x,f);
 8         ll res=x-f;
 9         ll add=(res+1)/2;
10         x+=add;
11         f=f+add*3-x;
12     }
13 }
View Code

 ac代码如下:

 1 #include<math.h>
 2 #include<stdio.h>
 3 #include<string.h>
 4 #include<stdlib.h>
 5 #include<algorithm>
 6 using namespace std;
 7 typedef long long ll;
 8 /*f[1]=0;
 9 f[n]=(f[n-1]+3)%n
10 c[n]=c[f[n]+1]*/
11 ll fj[]={1ll,2ll,3ll,4ll,6ll,9ll,14ll,21ll,31ll,47ll,70ll,105ll,158ll,237ll,355ll,533ll,799ll,1199ll,1798ll,2697ll,4046ll,6069ll,9103ll,13655ll,20482ll,30723ll,46085ll,69127ll,103691ll,155536ll,233304ll,349956ll,524934ll,787401ll,1181102ll,1771653ll,2657479ll,3986219ll,5979328ll,8968992ll,13453488ll,20180232ll,30270348ll,45405522ll,68108283ll,102162425ll,153243637ll,229865456ll,344798184ll,517197276ll,775795914ll,1163693871ll,1745540806ll,2618311209ll,3927466814ll,5891200221ll,8836800331ll,13255200497ll,19882800745ll,29824201118ll,44736301677ll,67104452515ll,100656678773ll,150985018159ll,226477527239ll,339716290858ll,509574436287ll,764361654431ll,1146542481646ll,1719813722469ll,2579720583704ll,3869580875556ll,5804371313334ll,8706556970001ll,13059835455001ll,19589753182502ll,29384629773753ll,44076944660629ll,66115416990944ll,99173125486416ll,148759688229624ll,223139532344436ll,334709298516654ll,502063947774981ll,753095921662471ll,1129643882493707ll,1694465823740560ll,2541698735610840ll,3812548103416260ll,5718822155124390ll,8578233232686585ll,12867349849029878ll,19301024773544817ll,28951537160317225ll,43427305740475838ll,65140958610713757ll,97711437916070635ll,146567156874105953ll,219850735311158929ll,329776102966738394ll,494664154450107591ll,741996231675161386ll};
12 ll as[]={0,1,1,0,0,0,1,1,0,1,0,0,1,1,0,1,0,1,0,0,1,1,0,1,0,0,1,0,1,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,0,1,0,1,1,1,1,1,0,0,1,1,0,1,0,1,1,0,1,0,1,0,0,1,0,0,1,1,1,1,0,1,1,0,1,1,1,1,1,1,0,1,0,0,0,0,0,1,1,0,1,1,0,1,0,1,1,0};
13 const int xn=102;
14 ll f(ll n)
15 {
16     int l=-1,r=xn;
17     while(l+1<r)
18     {
19         int mid=(l+r)/2;
20         if(fj[mid]>n)
21             r=mid;
22         else l=mid;
23     }
24     return as[l]+(n-fj[l])*3;
25 }
26 ll c(ll n,ll m,ll pre)
27 {
28     if(pre==n)return pre;
29     if(m==0)return n;
30     return c(f(n)+1,m-1,n);
31 }
32 void db()
33 {
34     ll x=1;
35     ll f=0;
36     while(x<=(ll)1e18)
37     {
38         printf("%lld %lld\n",x,f);
39         ll res=x-f;
40         ll add=(res+1)/2;
41         x+=add;
42         f=f+add*3-x;
43     }
44 }
45 int main()
46 {
47     ll t;
48     scanf("%lld",&t);
49     while(t--)
50     {
51         ll n,m;
52         scanf("%lld%lld",&n,&m);
53         printf("%lld\n",c(n,m,n+1));
54     }
55     return 0;
56 }
View Code

 

转载于:https://www.cnblogs.com/xseventh/p/8547139.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值