专栏文章
JOISC2019 l 鉱物 (Minerals)
AT_joisc2019_l题解参与者 2已保存评论 1
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 1 条
- 当前快照
- 1 份
- 快照标识符
- @mipl6ilb
- 此快照首次捕获于
- 2025/12/03 13:48 3 个月前
- 此快照最后确认于
- 2025/12/03 13:48 3 个月前
最后的优化稍微不太一样。
题意
个带编号带颜色的球,总共 种颜色,每种颜色恰好有两个球。
交互库有一个球的集合 ,一开始为空。每次交互操作时,选手程序向交互库给出一个编号 ,交互库改变 这个球在 中的状态:如果原本不存在就放进去,不然就拿出来。然后交互库返回 中的颜色数量。
要求在 次操作内求出每一对颜色相同的球的编号。
。
交互库有一个球的集合 ,一开始为空。每次交互操作时,选手程序向交互库给出一个编号 ,交互库改变 这个球在 中的状态:如果原本不存在就放进去,不然就拿出来。然后交互库返回 中的颜色数量。
要求在 次操作内求出每一对颜色相同的球的编号。
。
解法
一开始容易想到分治。这部分可以参考 DaiRuiChen007 大神的题解。
考虑交互能做什么:如果颜色总数与上一次相比没有变化,说明在操作前和操作后的 中,都存在一个球与询问的 颜色一样;不然就说明这样的球不存在。
这样容易用 次操作求出两个集合,满足两个集合里面 种颜色的球恰好各出现一次。
考虑交互能做什么:如果颜色总数与上一次相比没有变化,说明在操作前和操作后的 中,都存在一个球与询问的 颜色一样;不然就说明这样的球不存在。
这样容易用 次操作求出两个集合,满足两个集合里面 种颜色的球恰好各出现一次。
接下来分治,维护两个集合 满足两个集合大小一样、颜色种类一样,这些颜色的两个球都恰好在 和 中各有一个。过程中保证任意一次分治开始时, 里所有的球全都在 中或全都不在 中,并且知道是全都在还是全都不在。
每次把 分成两半 和 ,对其中某一个集合整个交互一遍,使得 全都在 中而 全都不在,当然反过来也行。
然后把 中每个球拿去交互,返回值拿来判断这个球对应的同色球是在 中还是在 中,把 分成 和 。
然后把 中每个球拿去交互,返回值拿来判断这个球对应的同色球是在 中还是在 中,把 分成 和 。
比如说 全都在 中而 全都不在,则询问一个球 时,如果颜色总数发生变化(不论是增加还是减少都是如此,你乐意的话可以自行仔细分类讨论一下),都说明 对应的同色球不在 中,如果在的话颜色数不会变化。
全都不在 中而 全都在的情况是完全对称的,比较方便的想法是直接交换两个集合。
然后往 和 递归分治,容易发现仍旧满足一开始“ 里所有的球全都在 中或全都不在 中,并且知道是全都在还是全都不在”的要求。
并且很方便的是往这两边递归的过程中操作的球的颜色不同(任意一侧都完全由同色球对组成),所以操作互不干扰,返回时不需要撤销影响。
并且很方便的是往这两边递归的过程中操作的球的颜色不同(任意一侧都完全由同色球对组成),所以操作互不干扰,返回时不需要撤销影响。
每次递归交互了半个 和一整个 ,这样的复杂度是 ,过不去。
优化
考察这个复杂度的形式,不妨设我每次都把整个 交互一遍,考虑不把 设成 的一半,而是稍小一些,复杂度或许可能更优。
具体地,设 的大小为 倍 的大小(当然这个可以记为 ),则操作次数大概为 。
DaiRuiChen007 大神说可以动态规划求这个最优大小。
毛估估看一下发现 并没能取到最值。
据称求导或者三分一下,大概取到 最优。
不过实际上打个表就行了,因为式子其实是 ,一看一堆取整就知道精度高了没啥用。比如以 为步长枚举可能的 ,发现 就足够优秀了。
CPP据称求导或者三分一下,大概取到 最优。
不过实际上打个表就行了,因为式子其实是 ,一看一堆取整就知道精度高了没啥用。比如以 为步长枚举可能的 ,发现 就足够优秀了。
int n=43000;
int f[N];
int dfs(int n,db k){
if(n<=1)return 0;
if(f[n])return f[n];
return f[n]=n+ceil(n*k)+dfs(ceil(n*k),k)+dfs(n-ceil(n*k),k);
}
signed main(){
static const db eps=1e-4;
pair<int,db>res={inf,inf};
for(db k=eps;k<0.5;k+=eps){
int w=dfs(n,k);
res=min(res,pair<int,db>(w,k));
fill(f+1,f+n+1,0);
}
cout<<res.x<<" "<<res.y<<endl;
return 0;
}
所以让这个分治过程右偏,每次取 作为 的大小,发现还是过不去,需要 次交互。
考虑扫描 过程中,如果 与 之一已经填到预期的大小了,就可以直接把剩下部分塞到另一个集合里,不需要额外消耗交互次数。如果不放心,还可以随机打乱 和 。
估计一下就会觉得这个常数比较小,实际上跑出来最多的询问次数大概 左右。
CPPmt19937 rnd(random_device{}());
using bsi=basic_string<int>;
bool q(int x){
int w=Query(x);
static int lst=0;
return lst==w?0:(lst=w,1);
}
void dfs(bsi&L,bsi&R,bool t){
// for(int x:L)cerr<<x<<" ";
// cerr<<" | ";
// for(int y:R)cerr<<y<<" ";
// cerr<<endl;
assert(L.size()==R.size());
int n=L.size(),n0=ceil(n*0.36),n1=n-n0;
if(L.size()==1)
return Answer(L[0],R[0]);
shuffle(all(L),rnd),shuffle(all(R),rnd);
bsi L0=L.substr(0,n0),L1=L.substr(n0,n),R0,R1;
for(int x:L0)q(x);
if(!t){
for(int y:R){
if((int)R0.size()!=n0&&(int)R1.size()!=n1)
(q(y)?R1:R0)+=y;
else ((int)R0.size()!=n0?R0:R1)+=y;
}
dfs(L0,R0,1),dfs(L1,R1,0);
}
else{
for(int y:R){
if((int)R0.size()!=n0&&(int)R1.size()!=n1)
(q(y)?R0:R1)+=y;
else ((int)R0.size()!=n0?R0:R1)+=y;
}
dfs(L0,R0,0),dfs(L1,R1,1);
}
}
void Solve(int n){
bsi L,R;
for(int i=1;i<=n*2;i++)
(q(i)?L:R)+=i;
dfs(L,R,1);
}
嘛,写动态规划也没有麻烦很多。
相关推荐
评论
共 1 条评论,欢迎与作者交流。
正在加载评论...