[CF1491F]Magnets

[CF1491F]Magnets

壹、题目描述 ¶

传送门 to CF.

官方题面

这是一个交互题。

早苗有 \(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

不要被询问次数蒙蔽的双眼......遇到特殊时刻时,应该考察这个时刻可能存在的性质。

posted @ 2021-05-16 11:28  Arextre  阅读(49)  评论(0编辑  收藏  举报