zoukankan      html  css  js  c++  java
  • 【UOJ83】【UR #7】水题出题人(提交答案题)

    点此看题面

    大致题意: 给你若干份排序的代码,共(6)个子任务,每个子任务让你构造数据使得一份代码用时在给定的(T)以内,另一份代码用时超过(2000000)

    子任务(1):归并排序(AC),计数排序(TLE)

    很简单,要想让计数排序(TLE),自然是要让值域尽量大。

    由于(T=7),因此(n)恰好为(1),则我们随便选取一个较大的数作为被排序的数即可。

    子任务(2):冒泡排序(AC),选择排序(TLE)

    这个子任务,我们可以选取一大堆相同的数,然后在里面混进一个不同的数,这样就能过了。

    至于数的总数混进数的位置,大概只能靠人肉二分了。

    造数据代码如下:

    #include<bits/stdc++.h>
    #define N 1990//数的总数
    #define M 1469//混进数的位置
    using namespace std;
    int main()
    {
    	freopen("Shion2.out","w",stdout);
    	register int i;cout<<N<<endl;
    	for(i=1;i^M;++i) cout<<19<<endl;//输出19(19是我的幸运数字)
    	cout<<24<<endl;//混进一个24
    	for(i=1;i<=N-M;++i) cout<<19<<endl;//接着输出19
    	return 0;
    }
    

    子任务(3):归并排序(AC),快速排序(TLE)

    经过测试,容易发现归并排序时间复杂度是稳定的。

    则通过人肉二分,我们可以求出最大的(n)(1984)

    然后就要考虑在这个限制之下如何卡快排。

    凭常识可知,快排的时间复杂度是很不稳定的,最坏情况下可以卡到(O(n^2)),而这样就足够了。

    考虑到在最坏情况下,我们应使中间的数最小(仔细去认真调试一遍快排的代码即可发现),而且每次操作后其实就相当于把最左边的数和中间的数换个位置

    所以,我就写了个递归函数模拟快排来求答案。

    造数据代码如下:

    #include<bits/stdc++.h>
    #define N 1984
    using namespace std;
    int p[N+5],ans[N+5];
    void DivideAndSolve(int l,int r,int v)//模拟快排来求答案
    {
    	int x=l+r>>1,i=l,j=x;!ans[p[x]]&&(ans[p[x]]=++v);//使中间的数最小
    	p[i]^=p[j]^=p[i]^=p[j],++i<r&&(DivideAndSolve(i,r,v),0);//交换最左边的数和中间的数
    }
    int main()
    {
    	freopen("Shion3.out","w",stdout);
    	register int i;cout<<N<<endl;for(i=1;i<=N;++i) p[i]=i;//初始化每个位置上是原序列中的第i个数(因为之后会换位置)
    	for(DivideAndSolve(1,N,0),i=1;i<=N;++i) cout<<ans[i]<<endl;//递归,然后输出答案
    	return 0;
    }
    

    子任务(4):计数排序(AC),冒泡排序(TLE)

    卡冒泡排序,需要构造逆序对。我们可以构造出一个序列,使其数字逐个递减。

    由于要让计数排序(AC)(T)较小,所以值域不能太大。

    因此我们可以使每个数重复多次,然后人肉二分数的总数序列中数的最大值(这样可以同时求出每个数字的重复次数),就可以了。

    造数据代码如下:

    #include<bits/stdc++.h>
    #define N 1012//数的总数
    #define M 30//序列中数的最大值
    using namespace std;
    int main()
    {
    	freopen("Shion4.out","w",stdout);
    	register int i,j;cout<<N<<endl;
    	for(i=M;i;--i) for(j=1;j<=N/M;++j) cout<<i<<endl;//递减输出每个数,且每个数重复多次
    	for(j=1;j<=N-(N/M)*M;++j) cout<<0<<endl;//由于不能恰好除尽,因此剩余的个数我们输出0
    	return 0;
    }
    

    子任务(5):选择排序(AC),冒泡排序(TLE)

    又是要卡冒泡排序,但这次没有了值域的限制,直接二分递归构造序列即可。

    造数据代码如下:

    #include<bits/stdc++.h>
    #define N 1004
    using namespace std;
    int ans[N+5];
    void DivideAndSolve(int l,int r,int v)//二分构造序列
    {
    	if(l>r) return;//当左边界大于右边界时,退出
    	int mid=l+r>>1;ans[mid]=v,//给中间的数赋值
    	DivideAndSolve(l,mid-1,v+r-mid+1),//处理左边
    	DivideAndSolve(mid+1,r,v+1);//处理右边
    }
    int main()
    {
    	freopen("Shion5.out","w",stdout);
    	register int i;cout<<N<<endl;
    	for(DivideAndSolve(1,N,0),i=1;i<=N;++i) cout<<ans[i]<<endl;//构造,然后输出
    	return 0;
    }
    

    子任务(6)(Bogo)排序(AC),快速排序(TLE)

    毫无疑问,是本题当中最恶心的一个子任务。

    首先,不难想到,这题的随机肯定有某种规律。

    于是便想到把这些数字一个个扔进计算器里看其二进制下的值,于是发现(RNG\_a)(RNG\_b)着两个参数差不多在模一个不太大的(2)的幂的情况下余(1)

    所以,它的交换数据这一部分的代码中的(j),其实就等于((seed+i+1)\%n)

    然后经过一定的测试,可得(n=4096)

    由于我们需要在一次之内能够排完序,所以我们倒序处理,就可以构造出原序列。

    而我们需要使构造出的序列能卡掉快排,试得(seed=2048)

    但是,(seed)是要根据(a_i)这个序列来算出的,而不是你随便就能定的。

    比较容易想到先求出前(n-1)个数使(seed)得到的值,然后找到一个合适的(a_n)使得(seed)能够恰好等于(2048),但这就需要枚举。

    可这个序列的相对大小又不能改变,因此我们枚举范围十分小。

    则可以考虑将已经构造出的序列中大于(a_n)的数全部加上一个很大的数,这样就能使得相对大小不变。然后再在这一较大的值域中进行枚举,就能找到了。

    造数据代码如下:

    #include<bits/stdc++.h>
    #define N 4096
    #define uint unsigned int
    #define swap(x,y) (x^=y^=x^=y)
    using namespace std;
    int a[N+5];
    int main()
    {
    	freopen("Shion6.out","w",stdout); 
    	register int i;register uint seed;
    	for(i=1;i<=N;++i) a[i]=i;for(i=N;i;--i) swap(a[i],a[(i+2048)%N+1]);//倒序处理,构造出原数列
    	for(i=1;i<=N;++i) a[i]>a[N]&&(a[i]+=1000000);//将已经构造出的序列中大于a[n]的数全部加上一个很大的数,这样就能使得相对大小不变
    	for(seed=2166136261,i=1;i^N;++i) seed=(seed*16777619)^a[i];//求出前n-1个数使seed得到的值
    	for(i=4092;i<=1004093;++i) if(((seed*16777619)^i)%N==2048) {a[N]=i;break;}//枚举一个合适的a[n]使得seed能够恰好等于2048
    	for(cout<<N<<endl,i=1;i<=N;++i) cout<<a[i]<<endl;//输出答案
    	return 0;
    }
    

    附录:答案

    以上就是这道题的全部解题思路了。

    点击下载答案

  • 相关阅读:
    React 之 jsx
    React 之 初识
    vue 之 svg
    c#进阶 之 特性
    c#进阶 之 反射Reflection
    面试题解答分析
    c#进阶 之 泛型
    c#进阶 之 修饰符
    c#进阶 之 方法汇总
    微信开发学习(二)
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/UOJ83.html
Copyright © 2011-2022 走看看