codeforces 835E The penguin's game

Codeforces 835E The penguin's game

题目传送门

知识点概要:

一道毒瘤交互题
给出你n个物品,其中有恰好两个物品是不同的,不同的物品的值是\(y\),其他的物品是\(x\)。每次可以询问一些物品的值的\(Xor\)值,让你在最多十九次的询问内找到这两个物品的位置。

知识点详解:

交互题可真好玩。。
我们先大力分讨一波:
1.如果取的区间为偶数,并且这个区间内有偶数个物品不同的时候,那么答案是0
2.如果取的区间为偶数,并且这个区间内只有奇数个物品不同的时候,那么答案\(x\) ^ \(y\)
3.如果取的区间为奇数,并且这个区间内有偶数个物品不同的时候,那么答案是\(x\)
4.如果取的区间为奇数,并且这个区间内只有奇数个物品不同的时候,那么答案是\(y\)
然后我们会发现一个问题,就是这有两个物品就很**了。。因为当我们得到的答案是\(0\)或者\(x\)的时候,我们无法分辨这个区间内到底有没有不同的物品了。
所以我们先考虑如果只有一个物品不同的时候,该怎么找到答案。如果只有一个物品的话,那么我们就可以二分这个区间来查找答案,如果答案返回的是\(y\)或者\(x\) ^ \(y\),那么说明这个区间内一定有一个不同的物品,这样我们就可以只需要\(log(n)\)次就可以查出一个的答案了。但是实际上我们恰好有两个不同的物品,所以我们考虑把这些物品分成两个集合,每个集合各包含一个不同的物品,这样我们就可以像上面那样得到每一个的答案了。但是我们会发现两个问题:
1.怎么分出这两个集合?
我们可以这样尝试去询问区间,枚举二进制的位数\(i\),然后每次询问都询问编号在二进制表示下第\(i\)位为1的数所组成的集合,如果返回值为\(y\)或者\(x\) ^ \(y\),则说明这个区间内只有一个不同的数,所以这一位就可以作为区分两个集合的一个标准了。只需要任意去这种位数不同的地方所组成的集合就行了。
2.询问次数如何保证?
我们会发现,枚举二进制编号最多需要询问\(log(n)=10\)次,然后在每一个集合中二分则需要询问\(log(n/2)=9\)次,这样我们总共需要询问28次,是不能满足要求的。但是我们可以观察到实际上第二次二分是没有必要的,因为我们只需要一次二分,找到第一个不同的物品的位置,然后再根据第一次枚举二进制编号找到的两个位置编号在二进制下不同的位置,用一个数\(diff\)表示这个状态,最后我们的第二个物品的位置就可以由\(pos1=pos2\)   \(Xor\)   \(diff\)来得到,这样我们总共的询问次数就刚好是19次,就可以满足要求了。

Code

#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int n,x,y;
vector<int>A,B;
int vis[N],ask[N];

void Getans(int x,int y) {
  if(x>y) swap(x,y);
  printf("! %d %d\n",x,y);
  return ;
}

int ReadIn() {
  int read;
  scanf("%d",&read);
  if(read==-1) exit(0);
  return read;
}

int Ask(vector<int>p) {
  if(!p.size()) return 0;
  printf("? %d ",(int)p.size());
  for(int i=0;i<p.size();i++) {
    printf("%d ",p[i]);
  }
  puts("");
  fflush(stdout);
  return 1;
}

int Solve(vector<int>a) {
  int l=0,r=a.size(),ans;
  while(l+1<r) {
    int mid=(l+r)>>1;
    vector<int>Q;
    for(int i=0;i<mid;i++) {
      Q.push_back(a[i]);
    }
    Ask(Q);
    int ret=ReadIn();
    if(ret==y||ret==(x^y)) r=mid;
    else l=mid;
  }
  return a[l];
}

int main() {
  scanf("%d%d%d",&n,&x,&y);
  int diff=0,diffbit=-1;
  for(int bit=0;bit<=9;bit++) {
    vector<int>a;
    a.clear();
    for(int i=1;i<=n;i++) {
      if((1<<bit)&i) a.push_back(i);
    }
    int f=Ask(a);
    if(!f) continue;
    int ret=ReadIn();
    if(ret==y||ret==(x^y)) {
      diff|=(1<<bit);
      diffbit=bit;
    }
  }
  for(int i=1;i<=n;i++) {
    if((1<<diffbit)&i) A.push_back(i);
    else B.push_back(i);
  }
  if(A.size()>B.size()) swap(A,B);
  int pos1=Solve(A);
  int pos2=pos1^diff;
  Getans(pos1,pos2);
  return 0;
}

posted @ 2018-09-17 07:41  Apocrypha  阅读(333)  评论(0编辑  收藏  举报