[CF1491F]Magnets
壹、题目描述 ¶
官方题面
这是一个交互题。
早苗有 \(n\) 块磁石,编号为 \(1,2,\cdots,n\). 每块磁石的磁极可能是正极,负极,也可能没有磁性。她希望你能帮她找出所有没有磁性的磁石。
万幸的是,你有一台磁力检测仪。你每次可以将每个磁石放在这台机器的左托盘,右托盘或者不放。
机器将会返回此时的磁力强度。设托盘左边有 \(n_1\) 个磁石为正极,\(s_1\) 个磁石为负极,托盘右边中有 \(n_2\) 磁石为正极,\(s_2\) 个磁石为负极,则返回的磁力强度为 \(n_1n_2+s_1s_2-n_1s_2-n_2s_1\).
如果一次测试中磁力强度的绝对值大于 \(n\),这台机器就会坏掉。
你需要在 \(n+\lfloor\log_2n\rfloor\) 次测试内找到所有没有磁性的磁石的编号,同时不能弄坏机器。
贰、题解 ¶
真的是个妙妙题,本来以为是在 \(\log\) 的复杂度下找到带有磁性的磁铁......结果......倒打一耙......
不难发现,我们只需要找到一个带有磁性的磁铁,然后一个一个地试其他的磁铁,就可以得到所有退磁的磁铁。
接下来,给一些提示有助于你的思考:
Hint-1. 能否找到一种方法在 \(2n\) 次询问内解决问题?
Hint-2. 能否找到第二个带有磁性的磁铁?如何找?找到的时候发现了什么性质了吗?
Hint-3. 实际的询问上限是 \(n-1+\lceil \log_2n \rceil\).
题解
发现式子实际上就是 \((n_1-s_1)(n_2-s_2)\).
我们可以这样来找到一个我们想要的磁铁:
首先,我们把第一个磁铁放在机器左边,然后,把下一个(第二个)磁铁放在右边。
如果机器返回的是 \(0\),则说明左右两边一定有一个(或一堆)呈“电中性”,那么我们把放在右边的那个磁铁放到左边去,然后再把下一个放到右边去比较,递归这个过程。换句话说,对于磁铁 \(i=2,\cdots ,n\),我们依次询问 \([1,i-1]\) 与 \(i\) 磁铁的力大小。显然,这样询问不会让机器爆炸。
当某个时刻,发现有力了,我们就停止这个过程!显然,右边孤零零的第 \(i\) 个磁铁就一定“带电”了。
接下来我们就可以暴力一个一个地询问了!挨个从 \(1\) 问到 \(n\),但是这样我们需要花费 \(\mathcal O(2n)\) 的询问,这不好。
考察我们找到磁铁时刻的性质 —— 左边 \([1,i-1]\) 的磁铁当中,一定只有一个“带电”!
首先,因为有力,所以左边的一堆磁铁都肯定带电,其次,如果有大于等于两个磁铁带电,我们假设编号最小的两个磁铁编号为 \(j,k(1\le j<k<i)\),那么,当我们询问 \([1,k-1]\) 与 \(k\) 号磁铁时,机器返回的就肯定不是 \(0\),即我们在问到 \(k(k<i)\) 的时候就已经停止了,还和 \(i\) 有什么关系呢?
所以,我们只需要将 \([i+1,n]\) 的磁铁和 \(i\) 一个一个地问,找到“电中性”的,对于 \([1,i-1]\) 种藏着的那个带电磁铁,我们用二分来查找即可!
总共需要的询问次数 \(n-1+\lceil \log_2n \rceil\).
叁、参考代码 ¶
int n, sec;
vector<int>ans;
inline int query(int l1, int r1, int l2, int r2){
printf("? %d %d\n", r1-l1+1, r2-l2+1);
rep(i, l1, r1) printf("%d ", i); Endl;
rep(i, l2, r2) printf("%d ", i); Endl;
fflush(stdout);
return readin(1);
}
signed main(){
rep(tmp_fuck, 1, readin(1)){
n=readin(1);
rep(i, 2, n){
int ret=query(1, i-1, i, i);
if(ret){
sec=i; break;
}
}
ans.clear();
int l=1, r=sec-1, mid, res;
while(l<r){
mid=(l+r)>>1;
if(query(1, mid, sec, sec)) r=mid;
else l=mid+1;
}
rep(i, 1, sec-1) if(i!=l)
ans.push_back(i);
rep(i, sec+1, n){
int ret=query(sec, sec, i, i);
if(!ret) ans.push_back(i);
}
printf("! %d", ans.size());
for(int i: ans) printf(" %d", i);
Endl; fflush(stdout);
}
return 0;
}
肆、用到 の Trick
不要被询问次数蒙蔽的双眼......遇到特殊时刻时,应该考察这个时刻可能存在的性质。