zoukankan      html  css  js  c++  java
  • 约瑟夫-从模拟到毒瘤

    约瑟夫真是个好题。

    约瑟夫的题有模拟,递推的标签。于是有两大类算法,三种题目。

    入门练习类

    复杂度$Theta(NM)$

    一般作者为了显示是个入门题会出$10^3 sim 3 imes 10^4$的数据范围。(额,出到$10^5$仿佛还没见过)

    而且一般会问整个序列。

    模拟这个过程。

    于是,有例子:

    $5$个人报数,每报到$3$这个人就出列,下一个人从1开始报。

    使用$LaTeX$。分$4$步

    $$
    egin{array}{ccccc}
    1&2&3&4&5\
    1&2& ot3&4&5\
    ot1&2& ot3&4&5\
    ot1&2& ot3&4& ot5\
    ot1& ot2& ot3&4& ot5
    end{array}
    $$

    于是最后只有$4$号留下了。

    上个代码(模拟):

    #include<cstdio>
    int all[10000000];
    int main(){
    	int m,n;
    	int bs=0,out=0,num=0;
    //	out计数出去的人数
    //	num记录当前的,
    //	bs是报数计数器
        scanf("%d%d",&m,&n);
    	do{
    		++num;
    		if(num==m+1) num=1; //循环下标
    		if(all[num]==0) ++bs;//如果这个人没有出去,报数
    		else continue; //已经出去了,跳过
    		if(bs==n){//发现报到了
    		    bs=0;
    			all[num]=1;//干出去
    			++out;//计数
    			printf("%d ",num);//输出
    		}
    	}while(out!=m);//如果全出去了,结束。
    	return 0;
    }
    

    递推类

    时间复杂度$Theta(N)$

    于是$0<n,m<10^8$

    并且只问最后一个人的编号。

    于是递推。

    如果我们要求$1$个人的序列的答案,

    然后反向报数加人

    可以先求出两个人的序列,在一直推到$N$个人,

    每次重新编号,并且为了方便,我们把$[1,N]$平移到$[0,N-1]$。

    于是本来有一个人。

    后来的一个人排在了正确的位置上,

    因为是反向报数,所以其实这个人就是第一个出去的人。

    而且现在的两个人中的最后出去的人的编号是对的。

    于是一直推。

    $$f_i=(f_{i-1}+m)mod{i}$$

    为了防止编号越出$n$,还得取个模。

    放个代码(好短):

    #include <iostream>
    #include <cstdio>
    #define LL long long
    
    using namespace std;
    
    LL ans=0,n,m;
    int main(){
    	ans=0;
    	scanf("%lld%lld",&n,&m);
    	for(int i=1;i<=n;i++)
    		ans=(ans+m)%i;
    	printf("%lld
    ",ans+1);
    }
    

    于是还有另类的题(不过也是逆推)

    第$i$次报$i$个数

    不过也一样,只是把里面的改成

    for(int i=n-1;i>=0;i--)
    	ans=(ans+i)%(n-i+1);
    

     一样是逆推。

    毒瘤类:

    当作者不再冷静,就会把数据出成这个鬼样子:

    $Nleq 10^9,M leq 10^6$

    总之$Theta(N)$过不去了。

    那么我们就要想想为啥$M$这么小,

    于是可以利用类似分块(?)的思想。

    在$Theta(N)$的基础上,把不需要取模的那部分直接乘法加速。

    于是变成$Theta(M log N)$

    就终于干过毒瘤出题人啦。

    #include <iostream>
    #include <cstring>
    #include <cstdio>
    
    using namespace std;
    
    int m,n;
    int main(){
    	int T;
    	for(cin>>T;T;T--){
    		scanf("%d%d",&n,&m);
    		int id=0;
    		if(n>m){
    			for(int i=1;i<=m;i++)
    				id=(id+m)%i;
    			for(int i=m+1;i<=n;i++){
    				id=(id+m)%i;
    				int jumpl=(i-id)/(m-1);
    				if(i+jumpl<=n){
    					id=(id+jumpl*m)%(i+jumpl);
    					i+=jumpl;
    				}
    				else{
    					id=id+(n-i)*m;
    					break;
    				}
    			}
    		}else{
    			id=0;
    			for(int i=1;i<=n;i++)
    				id=(id+m)%i;
    		}
    		printf("%d
    ",id+1);
    	}
    }
    
  • 相关阅读:
    php 创建文件
    php xml格式对象 返回->对应格式数组
    php 将16进制数串转换为二进制数据的函数
    php 生成随机字符串
    高质量PHP代码的50个实用技巧:非常值得收藏
    php __FILE__,__CLASS__等魔术变量,及实例
    纯js上传文件 很好用
    XMLHttpRequest上传文件实现进度条
    Java BufferedReader、InputStream简介
    Java socket通信
  • 原文地址:https://www.cnblogs.com/kalginamiemeng/p/11625108.html
Copyright © 2011-2022 走看看