洛谷 P9916 「RiOI-03」Just a Q. (Easy ver.) 题解

前言

Div.2 Rank \(13\) 获奖了,题目也好评。

解法

题目链接:\(\color{Purple}\texttt{P9916「RiOI-03」Just a Q. (Easy ver.)}\)

从 Subtask \(3\) 出发,考虑构造 \(\prod(a_i+1)\) 这种形式,因为它可以便利地在加入一个新数维护当前乘积的正负性,进而推出该数的正负性;加入一个新数时只需枚举遍历过的每一个子集,对该子集加入该数后形成的集合作询问并存储,可以过 Subtask \(3\)。如果把 \(a_1+1\) 换成 \(a_1\)(即一开始不询问那个空集)那么就能把询问次数除以 \(2\),可以过 Subtask \(4\)

正解需要一个 trick:先前一个一个数的做法询问次数很多,考虑把一些数一起询问。具体地,使用二分,令 \(m\) 为二分中点,每次将 \(q_{[l,m)}\)(这里 \(m=\left\lceil\frac{l+r}{2}\right\rceil\))绑在一起进行询问。如果 \(Q=0\),那么仿照上面 Subtask \(4\)\(a_1\) 的处理方法把当前集合清空并仅加入 \([l,m)\),正负性直接询问;否则考虑这一段的乘积正负性只需将其询问的最终结果的符号 \(\mathrm{sgn}(Q')\) 与上一次的结果的符号 \(\mathrm{sgn}(Q)\) 比较,如果相同则这一段乘积为正,否则为负。

原来的集合可以用 std::vector 存储 std::pair 实现。

放代码:

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
char ask(vector<pii> a){
  vector<int> v; char c;
  for(auto [l,r]:a)
    for(int j=l;j<=r;j++)
      v.emplace_back(j);
  cout<<"? "<<v.size()<<' ';
  for(int i:v)cout<<i<<' ';
  cout<<endl,cin>>c;
  return c;
} // 单次询问
int main(){
  ios::sync_with_stdio(false);
  int t; cin>>t;
  while(t--){
    int n,k; cin>>n>>k>>n;
    int l=1,r=n; bool f=true; char s='+';
    vector<vector<pii> > a;
    while(l<r){
      int m=l+r+1>>1; char c;
      if(f){
        s='+',a={{make_pair(l,m-1)}};
        c=ask(a[0]),f=false;
      } // Q=0 时
      else{
        vector<vector<pii> > b;
        for(auto i:a){
          vector<pii> v=i;
          v.emplace_back(l,m-1);
          b.emplace_back(v);
          c=ask(v);
        } // 遍历并询问
        a.insert(a.end(),b.begin(),b.end());
        // 将产生的新集合加入 a 中
      }
      if(c==s)l=m; // 贡献为正,负数在右
      else if(c==48)r=m-1,f=true;
      else s=c,r=m-1; // 贡献为非正数,负数在左
    }
    cout<<"! "<<l<<endl;
  }
  return 0;
}
posted @ 2024-01-20 16:43  Physics212303  阅读(11)  评论(0编辑  收藏  举报