[WC2018]即时战略
做交互题还是建议去uoj或loj吧(loj暂时交不了),毕竟洛谷形式不规范。
我来做这题,只是纯粹地无聊。想起noip因为交互题爆炸,随便找了道交互,然后就是5h(事实上3.5h的时候我的代码就能AC了,直到5h我才交了一发)。
初步想法
首先,关于这个 explore
怎么用是要有一定想法的。
假设我们知道点 \(x\) 是已知点, 那么对于任意一个点 \(y\) 我们可以通过 \(\operatorname{dis}(x,y)\) 次explore
把整条路径上的点都变成已知点。每一次 explore
后令 \(x\) 为返回值 \(z\) , 直到 \(x=y\) 即可。
链
这个 dataType=3
一看就是要数据分治的,别的 \(T\) 都是 \(n\log n\) 级别的,只有这个是 \(n+\log n\) 级别的。
想了半天正解无果就来做这个部分分。
只有 \(21\) 次错误的机会,其余必须 explore
一次增加一个已知点。
整棵树都不知道怎么 \(\log\) 啊???
首先有个显然的 \(T\le 2n\) 的做法:
考虑到已知点一定是链上的一段区间 \([l,r]\),对于新加入的一个点 \(i\),如果 explore(l,i)
是未知点,那么它在 \(l\) 左边,否则在 \(r\) 的右边。然后用上面所述 explore
的用法可以直接更新左右端点并且把 \(i\) 融入已知点的区间。
所以每至多 \(2\) 次询问可以确定一个点。并且在 \(i\) 到已知区间的路径上的点只需要 \(1\) 次就可以变成已知点。
然后就要点脑洞了:随机化
如果每次随机取一个未知点并且把它归到区间内。发现每次未知点区间长度期望减少一半的长度,就可以 \(\log\) 了。
吐槽:我一开始忘记随机化还能过,说明出题人链的随机性不错(
说句闲话:\(\color{black}{\texttt{S}}\color{red}{\texttt{egmentTree}}\) 问我:你是不是用了 mt19937
,这个不是考场上不能用的吗?
于是我手写了随机数。后来我发现他用了 mt19937
(((
暴力
开始考虑前面的点暴力怎么拿。忽然发现对于每一个未知点执行上面所述 explore
的过程,从 \(1\) 扩展到这个未知点。结合上面 dataType=3
的数据分治可以通过前14个点得到70分。
正解
其实做到现在对于这个游戏怎么玩应该有一定想法了,有用的操作就是从一个已知点开始,它的下一步就是未知点,不断 explore
整个路径 。而最大点过不去的原因也正是因为上面的暴力会大面积重复遍历已知点。
也就是说我们要以极少的次数找到一个点 \(x\) ,使得 \(x\) 往我们需要扩展到的未知点 \(y\) 走,每一步走到的点都是未知点。换句话说,我们要找从 \(y\) 往当前的已知点联通块走,第一个遇上的已知点。
忽然脑袋里闪过了点分树,确实,这东西可以帮我们 \(\log n\) 次询问以内定位到 \(x\) 。在分治树上跑,每次explore(分治中心,y)
,从得到的点往上跳就能知道它在哪个分治子树,这样 \(\log n\) 次 以内就可以找到我们想要的点了。
但是这个点分树显然是动态加点的。然而WC前几年的一道题对于动态点分树起到了良好的普及效果:紫 荆 花 之 恋 。上次这题写的不顺利也是我有点怕动态点分树的原因。
点分树过于不平衡的时候替罪羊重构即可,不过多赘述做法了,不会的看那题去(
时间复杂度 \(O(n\log^2 n)\) ,询问次数 \(O(n\log n)\) 。
非常显然,\(n\log n > T\) ,但是这个 \(\log n\) 在开始点分树没有建满的时候跑不满,但是不知道可以被卡成什么样子,所以我还是随机取一个未知点进行扩展。
接下去说说我为啥多调了1.5h代码。因为我本地造了一个比较强悍的数据,就是链底下挂一个大菊花。其实这个数据过了基本上就稳了。
我的程序莫名其妙RE了,没有除法没有越界,在找重心的时候跑了一半就断错误了。我非常迷惑,找了大概40min想起来可能是没开栈,ulimit 512000000
。(你们都应该看的出来我开的是512G吧,显然开不下,相当于啥都没干)
RE到了下课,\(\color{black}{\texttt{F}}\color{red}{\texttt{orever_Pursuit}}\) 过来随手 ulimit 512000
就过了,一交就AC了。。。
代码稍微加了点注释,如果真的要看凑合着看吧。
#include<bits/stdc++.h>
#include "rts.h"
using namespace std;
#define pb push_back
#define sz(v) (int)v.size()
#define rep(i,x,y) for(int i=x,i##end=y;i<=i##end;++i)
#define per(i,x,y) for(int i=x,i##end=y;i>=i##dnd;--i)
template<class T>inline bool ckmax(T&x,T y){return x<y?x=y,1:0;}
template<class T>inline bool ckmin(T&x,T y){return x>y?x=y,1:0;}
typedef long long LL;
typedef double db;
const int N=300005;
int rd,a[N];
bool vis[N];
struct RAND{
const int basic=(1<<30)-1;
unsigned long long sa,sb,sc;
RAND(){
srand(time(0));
for(int i=0,up=::rand()%100;i<=up;++i)::rand();
srand(::rand()^clock()),sa=1ull*::rand()*::rand()*::rand();
for(int i=0,up=::rand()%100;i<=up;++i)::rand();
srand(::rand()^clock()),sb=1ull*::rand()*::rand()*::rand();
for(int i=0,up=::rand()%100;i<=up;++i)::rand();
srand(::rand()^clock()),sc=1ull*::rand()*::rand()*::rand();
}
int rand(){
sa^=sa<<32,sa^=sa>>13,sa^=sa<<1;
unsigned long long t=sa;
sa=sb,sb=sc,sc^=t^sa;
return sc&basic;
}
int rad(int l,int r){return rand()%(r-l+1)+l;}
}mk;
namespace data3{
void cov(int x,int y){
vis[x]=1;
while(x!=y)rd=explore(x,y),vis[x=rd]=1;
}
void main(const int&n){
int l=1,r=1;
rep(i,2,n){
int u=a[i];
if(vis[u])continue;
if(!vis[rd=explore(l,u)])cov(rd,u),l=u;
else cov(r,u),r=u;
}
}
}
namespace std{
const db alpha=0.75;
vector<int>e[N],to[N];
int fa[N],mx[N],rt,tsize,siz[N],reb,vt[N],sz[N],root;//sz是分治树的大小,vt是分治树上的父亲
bool used[N];
void getrt(int u,int ft){//找重心
siz[u]=1,mx[u]=0;
for(int i=0,up=sz(e[u]);i<up;++i){
int v=e[u][i];
if(v==ft||used[v])continue;
getrt(v,u),siz[u]+=siz[v];
ckmax(mx[u],siz[v]);
}
ckmax(mx[u],tsize-siz[u]);
if(mx[u]<mx[rt])rt=u;
}
void pia(int u){//拍扁,同时给要重构的点打标记
++tsize,used[u]=0;
for(int v:to[u])pia(v);
to[u].clear();
}
void dfs_build(int u){//普通建立点分树
used[u]=1,sz[u]=1;
for(int i=0,up=sz(e[u]);i<up;++i){
int v=e[u][i];
if(used[v])continue;
rt=0,tsize=siz[v],getrt(v,u);
int tmp=rt;//一定要存一下,rt在变
to[u].pb(rt),vt[rt]=u,dfs_build(rt),sz[u]+=sz[tmp];
}
}
void rebuild(int u){//重构
rt=0,tsize=0,pia(u);
getrt(u,0);
if(u==root)root=rt;
if(vt[u])
for(int i=0,up=sz(to[vt[u]]);i<up;++i)if(to[vt[u]][i]==u)to[vt[u]][i]=rt;
vt[rt]=vt[u];
dfs_build(rt);
}
void dfs_ins(int x,int ft){//点分树上插入一条边,ft已经在树上
used[x]=vis[x]=1;
fa[x]=vt[x]=ft,to[ft].pb(x),e[ft].pb(x),e[x].pb(ft);
for(int i=x;i;i=vt[i])++sz[i];
reb=0;
for(int i=x;vt[i];i=vt[i])if(sz[i]>1.*sz[vt[i]]*alpha)reb=vt[i];
if(reb)rebuild(reb);
}
void solve(int u,int t){//找到那个点并且加边
rd=explore(u,t);
if(!vis[rd]){
int x=rd;
dfs_ins(x,u);
while(x!=t){
rd=explore(x,t);
dfs_ins(rd,x);
x=rd;
}
return;
}
while(vt[rd]!=u)rd=vt[rd];
solve(rd,t);
}
void main(const int&n){
vis[1]=1,mx[rt=0]=n,sz[1]=1,used[1]=1,root=1;
rep(i,2,n){
int u=a[i];
if(vis[u])continue;
solve(root,u);
}
}
}
void play(int n,int T,int dataType){
vis[1]=1;
rep(i,2,n)a[i]=i;
rep(i,2,n)swap(a[i],a[mk.rad(2,n)]);//随机操作序列
if(dataType==3)data3::main(n);
else std::main(n);
}