今天SDOI二轮培训(没错我又来找虐了)
D1T1是一道交互题。
然而我还是太弱。经验也不够。rand检验次数太少。推到推导也不会,遂保龄。
然而大体思路还是差不多的(剩下的题去一边)
题面
Problem A. 排列谜题(perm.c/cpp/pas)
Input file: N/A
Output file: N/A
Time limit: 1 seconds Memory limit: 256 megabytes
这是一道交互题,本题仅支持 C++。
小 y 有一个排列 p,小 z 也很想要知道小 y 这个排列到底是什么。
小 z 只知道这是一个 0 到 n − 1 的排列,下标从 0 开始。小 y 不想告诉小 z 这个排列,于是他规定了小 z 只能向他提出一个固定的问题,小 z 可以向他提出 3 个 0 到 n − 1 中的数字 a, b, c,而小 y 会告诉他 (p^{-1}(x)=(p(a) ∗ p(b) + p(c)))(所有运算都是在 (mod~n) 意义下的)。其中 (p^{-1}(x)) 表示满足 p(y) = x 的 y。由于小 y 没有什么耐心,所以小 z 希望能够尽快得到答案。
Notes
下发文件中有 perm.hpp、sample.cpp 两个文件,以下是详细说明,如果懒得看的话你也可以直接参照 sample.cpp 编写代码。
你需要在你代码的开头 #include ”perm.hpp” 来与交互库交互,你不应该实现 main 函数或试图进行任何文件输入输出,你只需要实现一个函数:std::vector
你可以使用交互库提供的一个函数:int query_position(int a,int b,int c),这个函数接受 a, b, c,返回 (p^{-1}(x)=(p(a) ∗ p(b) + p(c)))。
样例交互库(下发的 perm.hpp)会从标准输入读入排列。交互库不会占用超过 0.25s 时间和 64MB 内存。
交互库是固定的,即交互库会在调用你的 guess 函数之前生成好排列,不会动态调整。数据包含若干组数据,你的得分为最小值。
对于每个数据,假设你的程序没有得出正确的排列或者询问了非法的内容,则获得 0 分。
否则如果你调用了不超过 12512 次,可以获得 100 分,如果调用了超过 1000000 次,则获得 0 分。否则假设你调用了 (time) 次询问,你的得分将是(frac{1251200}{time})
//perm.cpp
#include"perm.hpp"
#include<bits/stdc++.h>
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-funroll-loops")
#pragma GCC target("sse4")
#define mms(a,n) memset(a,0,sizeof((a)[0])*(n))
#define mmp(a,b,n) memcpy(a,b,sizeof((b)[0])*(n))
#define lowbit(x) ((x)&-(x))
#define pb push_back
#define fi first
#define se second
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define fo(i,l,r) for(register int i=l,_lim_=r;i<=_lim_;i++)
#define fd(i,r,l) for(register int i=r,_lim_=l;i>=_lim_;i--)
#define fos(i,l,r,d) for(register int i=l,_lim_=r;i<=r;i+=d)
#define fol(i,l,r) for(register ll i=l,_lim_=r;i<=_lim_;i++)
#define fdl(i,r,l) for(register ll i=r,_lim_=l;i>=_lim_;i--)
#define fosl(i,l,r,d) for(register ll i=l,_lim_=r;i<=r;i+=d)
#define Clear(a) memset(a,0,sizeof(a))
#define Copy(a,b) memcpy(a,b,sizeof(b))
#define ALL(v) v.begin(),v.end()
#define SZ(v) ((int)v.size())
#define sqr(x) ((x)*(x))
#define GCD __gcd
#define rep(i,a,n) for (int i=a;i<n;i++)
#define per(i,a,n) for (int i=n-1;i>=a;i--)
using namespace std;
typedef unsigned int uint;
typedef unsigned long long ull;
typedef long long ll;
typedef long double ldb;
typedef double db;
typedef pair<int,int> pi;
typedef vector<int> VI;
typedef vector<VI> VII;
const int N=5005;
int n,p[N],nex[N],_0,_n1,_1,v[N][N];
std::vector<int> guess(int n){
vector<int> Ans;
if(n==1){
Ans.push_back(0);
return Ans;
}
memset(v,-1,sizeof(v));
fo(i,0,n-1)p[i]=-1;
fo(i,0,n-1){
bool ok=true;
fo(j,0,min(n-1,1000)){
if(v[i][j]==-1)v[i][j]=query_position(i,j,j);
if(v[i][j]!=j){ok=false;break;}
}
if(ok){
_0=i;
break;
}
}
fo(i,0,n-1)if(i!=_0){
bool ok=true;
fo(j,0,min(n-1,1000)){
if(v[i][j]==-1)v[i][j]=query_position(i,j,j);
if(v[i][j]!=_0){ok=false;break;}
}
if(ok){
_n1=i;
break;
}
}
p[_n1]=n-1;p[_0]=0;
_1=query_position(_n1,_n1,_0);
p[_1]=1;
fo(i,0,n-1)nex[i]=query_position(_1,i,_1);
for(int i=_1;i!=_n1;i=nex[i])p[nex[i]]=p[i]+1;
for(int i=0;i<n;++i)Ans.push_back(p[i]);
return Ans;
}
//sample
#include"perm.hpp"
#include<bits/stdc++.h>
using namespace std;
std::vector<int> guess(int n){
vector<int> A;
A.push_back(0);
if(n==1)return A;
}
(Solution)
首先我们从一些具有特殊性质的数字找起:(0~and~1)
为什么? 首先(0)可以使得返回的数据中的(p(a)p(b))变为(0),直接返回(p(c))的位置
然后我们找(1),对于查询(pos[1],x,pos[0]),就直接返回(x+1)的位置,利用这个我们可以推出所有的答案。
然后这个查询次数好像是(3n+)一些错误的查询,据大佬分析,好像是在(15000~6000)之间波动(TQL
然后就是加速。
怎么加速呢?首先我们要记录,不能重复查重复的次数。然后我们要重复利用有限的信息。
如何利用,其实我们大部分的查询都花费在了搜寻(1~and~0)上了。而且这不能重复利用。
查询(0)时,我们调用的是(query_position(i,j,j)==j)i为当前的下标,j为枚举进行检验的下标
搜寻(1)时,我们调用的是(query_positon(i,j,pos[0])),i为当前的下标,j为所枚举进行检验i的下标,pos[0]为数值0所在的下标(后同
但我们似乎却忘了一个问题,这是在模意义下的。
为什么不搜索-1呢?啥?为什么
我们查询(-1)时,所调用的是(query_position(i,j,j)==pos[0]),可以看出,是和查询(0)时的参数是一样的。在加上记忆,差不多就能剩下许多的查询
然后因为有特殊情况,需要多次判断,这里没听懂,听说是(2sqrt{n})的
然后因为我太菜了。没有标程,大佬的厚不下脸皮来贴,就不贴了233