1 条题解
-
0
神秘题,感觉是我一辈子也会不了的那种。
建图考虑先建一个随机的存在哈密顿路的图。
首先我们先尝试构造一条哈密顿链。构造方法也很简单,一开始是 个长度为 的链,然后钦定其中一条为主链,每次随机尝试将某一条链拼接到主链后面,因为图随机所以有 的成功率,期望 步操作可以合并一条链。
最后可能会剩下若干条链合并不了,因为每两条链都有 的概率可以合并在一起(两种顺序,每种 ),所以剩下的链的数量是 条,我们随机劈开一条链然后重新尝试即可,因为一开始保证了存在哈密顿路所以一定可以成功,容易发现这一步需要期望 次询问。
然后我们还需要把这条链变成一个环,假设这条链原本是 的,考虑找到链上最靠近 的 满足存在边 ,此时 构成了一个环,然后尝试将 后面的点按照链上的顺序插入这个环中。
假设上一个插入的点是 (初始为 ), 在环上的下一个点是 ,这次要插入的点是 ,那么尝试将如果存在 的边,又因为原本链上存在 的边,所以可以把 插入 中间。否则就存在 的边,把 换成 重复这个过程。因为存在哈密顿路,所以图强连通,所以环上必然存在一个 使得存在 的边,也就是若干步之后一定可以成功。
容易发现这个部分需要插入的点数是 的,每次插入也是 的,总询问次数 。
考虑这个方法的询问次数,第一部分构造链需要合并 次,每次期望 个询问,第二部分链变成环期望 个询问,所以我们就得到了一个期望 次询问的做法。
考虑优化,注意到合并两个长为 的链其实只需要 次询问且必然成功,所以我们一开始可以先合并成 条长度为 的链,然后在继续合并,此时操作次数就是 了。
但是因为限制是恰好 ,所以多了 次操作无法通过。
考虑进一步优化,首先我们不再随机建图,考虑这样建图,首先构造一个 元环,然后如果两个点 , 出发顺势针走到 的距离不超过 ,那么连接 ,否则连接 。
此时我们随机合并两条链的成功率依旧是 。
但是当我们尝试把 开头的链拼到 结尾的链后面失败之后,那么 在那个环上顺时针到 的距离一定超过 。此时如果我们向 后面成功拼接了一个长度为 的链,那么 所在链的末尾就向在环上顺时针走 步,每步长度在 中随机,简单积分一下发现,此时后面能接上 的概率从 变成了 。
加入此优化后根据实现不同就可以做到 次操作左右。
当然,我们注意到,如果我们在每次合并失败,分裂一条链的时候,判断一下是否存在单点的链,如果存在就改为尝试将这个点按照以下方式插入主链中。
假设主链是 ,单点是 。
如果存在 ,那么插入到结尾。如果存在 的边,那么插入到开头。否则链上一定存在两个相邻的位置 ,使得存在 的边,插入这两者中间。
我们容易发现,这样只需要期望不超过 次询问,当然实际情况下因为无法插入时,插入结尾的情况通常已经判断过了,所以实际询问次数的期望更少。
加入这个优化后可以做到 次询问左右。具体的期望操作次数不是很算的清楚。根据实测的结果,按照直线拟合了一下我寻思操作次数是 左右。
代码:
#include<bits/stdc++.h> #define N 509 #define pb push_back #define ll long long using namespace std; mt19937 rd(chrono::steady_clock::now().time_since_epoch().count()); int rnd(int x,int y){ ll t=rd(); return t%(y-x+1)+x; } int n; int a[N][N]; int query(int x,int y){ if(x==y)return 0; if(a[x][y]||a[y][x])return a[x][y]; cout<<"? "<<x<<" "<<y<<"\n"; char c;cin>>c; if(c=='>')a[x][y]=1; else a[y][x]=1; return a[x][y]; } vector<vector<int>> q,rj[2]; void insert(vector<int> &A,int x){ if(query(A.back(),x))A.pb(x); else if(query(x,A.front()))A.insert(A.begin(),x); else{ for(int i=0;i+1<A.size();i++){ if(query(A[i],x)&&query(x,A[i+1])){ A.insert(A.begin()+i+1,x);return; } } } } void split(){ int mx=0;for(auto t:q)mx=max(mx,(int)t.size()); if(mx<=1)return;int t=rnd(0,q.size()-1); while(q[t].size()<=1)t=rnd(0,q.size()-1); int x=rnd(1,q[t].size()-1); q.emplace_back(q[t].begin()+x,q[t].end()); q[t].erase(q[t].begin()+x,q[t].end()); } void cl(){ for(auto t:rj[0])q.pb(t); for(auto t:rj[1])q.pb(t); rj[0].clear();rj[1].clear(); } void solve(){ q.clear();rj[0].clear();rj[1].clear(); for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++)a[i][j]=0; } for(int i=1;i<=n;i+=2){ if(i+1<=n){ int t=query(i,i+1); if(t)q.pb({i,i+1}); else q.pb({i+1,i}); } else q.pb({i}); } int ok=0,fl=0; int u=0; while(q.size()+rj[0].size()+rj[1].size()>1){ auto &r=rj[ok^1]; if(!r.empty()){ if(query(q[0].back(),r.back().front())){ for(int x:r.back())q[0].pb(x); r.pop_back();ok^=1; for(auto t:r)q.pb(t); r.clear(); } else{ q.pb(r.back());r.pop_back();++fl; } continue; } if(q.size()<=1)cl(); u=rnd(0,q.size()-1); if(!u)continue; if(query(q[0].back(),q[u].front())){ for(int x:q[u])q[0].pb(x); q.erase(q.begin()+u); ok^=1; for(auto t:rj[ok])q.pb(t); rj[ok].clear(); } else if(++fl>=n&&fl>=5){ bool t=0; for(int i=1;!t&&i<q.size();i++){ if(q[i].size()<=1){ t=1; for(int x:q[i])insert(q[0],x); q.erase(q.begin()+i); } } if(!t)split(); fl=0; } else{ swap(q[u],q.back()); rj[ok].pb(q.back()); q.pop_back(); } } cl();vector<int> A=q.front(); int t=n-1;while(!query(A[t],A[0]))--t; vector<int> ans; for(int i=0;i<=t;i++)ans.pb(A[i]); for(int i=t+1,j=t,k=0;i<n;i++){ while(!query(A[i],ans[k])){ j=(j+1)%i;k=(k+1)%i; } ans.insert(ans.begin()+j+1,A[i]); j=j+1;k=(j+1)%(i+1); } cout<<"! ";for(int x:ans)cout<<x<<" ";cout<<"\n"; } int main(){ int T;cin>>n>>T; for(int i=1;i<=n;i++){ for(int j=i+1;j<=n;j++){ if(j-i<=(n/2))a[i][j]=1,a[j][i]=0; else a[i][j]=0,a[j][i]=1; } } for(int i=1;i<=n;i++)a[i][i%n+1]=1,a[i%n+1][i]=0; for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++)cout<<a[i][j],a[i][j]=0; cout<<"\n"; } while(T--)solve();return 0; }
- 1
信息
- ID
- 12557
- 时间
- 10000ms
- 内存
- 700MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者