题解:CF2129C Interactive RBS

非常好玩的一道题!

首先不难想到首先要找到一对括号,然后再次基础上询问其它的。简单拆分一下询问次数, 550 = 2 × 20 + 500 + eps 550 = 2 \times 20 + 500 + \text{eps} 550=2×20+500+eps,发现在找到括号后需要询问一次处理两个括号。至于为什么是 2 × 20 2 \times 20 2×20,原因便是在 [ l , r ] [l,r] [l,r] 中即使有左右括号,它可能以 () \texttt{()} () 或 )( \texttt{)(} )( 的形式出现,因此需要正反各询问一次。

接下来考虑如何一次询问处理两个括号。其实方法很多,适合手玩,用上 substring 这个条件,尽量构造不对称括号序列即可。我构造的是 ()??() \texttt{()??()} ()??(),有以下几种情况:

a n s = { 1 ((((() 2 (()(() 3 ((()() 4 (())() ans = \begin{cases} 1\ \ \ \ \texttt{((((()} \\ 2\ \ \ \ \texttt{(()(()} \\ 3\ \ \ \ \texttt{((()()} \\ 4\ \ \ \ \texttt{(())()} \\ \end{cases} ans=⎩ ⎨ ⎧1 ((((()2 (()(()3 ((()()4 (())()

设 L , R L,R L,R 为二分找到的左/右括号位置,以第一个为例子,查询的写法为 query ({L,L,x,y,L,R}。注意,如果剩下未确定的数量是奇数,需要特殊考虑。


询问次数变为 200 200 200 以内,需要一次至少测 6 6 6 个。

如果接着从上一节的询问出发,直接赋值询问序列,会发现情况并不会显著增长,原因就是受到相互影响。所以进一步思考如何才能独立考虑。

尝试构造 (?((?((?(... \texttt{(?((?((?(...} (?((?((?(... 的序列。具体来说,用二进制数的思想,第 i i i 个待检测位置出现 2 i − 1 2^{i - 1} 2i−1 次,即 (x((y((y((z((z((z((z(... \texttt{(x((y((y((z((z((z((z(...} (x((y((y((z((z((z((z(...。得到答案后,从低往高第 i i i 位为 1 1 1,则说明第 i i i 个待检测的位置为 ) \texttt{)} ),否则为 ( \texttt{(} (。当然,若剩余待检测位置的数量不足,直接用 L L L 补齐即可,处理的时候特判。

最后检验一下合法性。若一次检测 x x x 个,询问的长度为 3 ( 2 0 + 2 1 + ⋯ + 2 x − 1 ) = 3 ( 2 x − 1 ) 3(2^0 + 2^1 + \cdots + 2^{x - 1}) = 3(2^x - 1) 3(20+21+⋯+2x−1)=3(2x−1)。由于长度不能超过 1000 1000 1000,因此 x max ⁡ = 8 x_{\max} = 8 xmax=8,可以通过此题。


询问次数变为 100 100 100 以内,需要一次至少测 13 13 13 个。

接着上一节的想法,尝试构造互不影响的序列。尝试构造序列 (x((y(y((... \texttt{(x((y(y((...} (x((y(y((...,组内用 ( \texttt{(} ( 分隔,组间用 (( \texttt{((} (( 分隔。设第 i i i 个待检测的位置出现 d i g i dig_i digi 次,则对答案的贡献为 s u m i = ( d i g i + 1 ) d i g i 2 sum_i = \frac{(dig_i + 1)dig_i}{2} sumi=2(digi+1)digi。进一步来说,只要满足下式条件即可:

∄ i ∈ I s.t. s u m i = ∑ j ∈ J s u m j \nexists i \in I \quad \text{s.t.} \quad sum_i = \sum_{j \in J} sum_j ∄i∈Is.t.sumi=j∈J∑sumj

其中 I = { 1 , 2 , ⋯   , ∣ s u m ∣ } I = \{1,2,\cdots,|sum|\} I={1,2,⋯,∣sum∣}, J ⊆ I ∖ { i } J \subseteq I \setminus \{i\} J⊆I∖{i},且满足 ∀ j , k ∈ J , j ≠ k \forall j,k \in J, j \neq k ∀j,k∈J,j=k。

容易发现,当 2 s u m i − 1 ≤ s u m i 2sum_{i - 1} \le sum_i 2sumi−1≤sumi 时可以满足条件,通过暴力打表发现,最大的符合题目限制的 d i g dig dig 集合为 { 1 , 2 , 4 , 6 , 9 , 13 , 19 , 28 , 40 , 57 , 81 , 115 } \{1,2,4,6,9,13,19,28,40,57,81,115\} {1,2,4,6,9,13,19,28,40,57,81,115}。但是此时 ∣ d i g ∣ = 12 |dig| = 12 ∣dig∣=12,按照原来的二分方式会超过限制。仔细思考可以发现原来的二分每一次的 check 需要花费 2 2 2 次询问,但是我们只想要知道序列中是否存在左右括号以及括号的方向。因此,当方向未确定时,我们将询问的序列正反拼接,得到有无左右括号的信息。一旦在某次询问中得知存在左右括号,直接花费额外的一次询问定方向。在得知方向后,每次二分只需要一次询问去缩小范围。二分的询问次数的上确界为 ⌈ log ⁡ ( 1 0 3 ) + 1 ⌉ = 11 \lceil{\log (10^3)} + 1 \rceil = 11 ⌈log(103)+1⌉=11 次,总询问次数的上确界为 11 + ⌈ 1 0 3 12 ⌉ = 11 + 84 = 95 11 + \lceil \frac{10^3}{12} \rceil = 11 + 84 = 95 11+⌈12103⌉=11+84=95 次,同时单次询问长度不会超过 1000 1000 1000,已经可以通过此题。

但能否进一步优化呢?按照之前的互不影响的条件去构造基,可以尝试写一个 O ( k 2 2 k ) O(k^2 2^k) O(k22k) 的状压构造并手动调整,在此不过多赘述,直接给出一组长度为 13 13 13 的可行构造: { 1 , 2 , 4 , 5 , 8 , 11 , 16 , 23 , 33 , 57 , 74 , 105 , 150 } \{1,2,4,5,8,11,16,23,33,57,74,105,150\} {1,2,4,5,8,11,16,23,33,57,74,105,150},可以证明不存在比 13 13 13 更大且满足条件限制的基。此时总询问次数的上确界可以降为 11 + ⌈ 1 0 3 13 ⌉ = 11 + 77 = 88 11 + \lceil \frac{10^3}{13} \rceil = 11 + 77 = 88 11+⌈13103⌉=11+77=88 次,理论上应该已经达到最优解了。


完整代码如下:

cpp 复制代码
#include <bits/stdc++.h>
#define init(x) memset (x,0,sizeof (x))
#define ll long long
#define ull unsigned long long
#define INF 0x3f3f3f3f
#define pii pair <int,int>
using namespace std;
const int MAX = 1e5 + 5;
const int MOD = 1e9 + 7;
const int UP = 13;
inline int read ();
int dig[] = {1,2,4,5,8,11,16,23,33,57,74,105,150};
int query (vector <int> lst)
{
    printf ("? %d ",(int)lst.size ());
    for (auto v : lst) printf ("%d ",v);
    puts ("");fflush (stdout);
    return read ();
}
void solve ()
{
    int n = read ();
    int l = 1,r = n,L = -1,R = -1,sure = -1;
    vector <char> ans (n + 1);
    auto check = [&] (int l,int r) -> bool
    {
        vector <int> lst;
        for (int i = l;i <= r;++i) lst.push_back (i);
        if (sure == 0) return query (lst);
        else if (sure == 1)
        {
            reverse (lst.begin (),lst.end ());
            return query (lst);
        }
        for (int i = r;i >= l;--i) lst.push_back (i);
        if (!query (lst)) return false;
        for (int i = l;i <= r;++i) lst.pop_back ();
        if (query (lst)) sure = 0;
        else sure = 1;
        return true;
    };
    while (1)
    {
        if (l + 1 == r)
        {
            if (sure == -1) 
            {
                if (query ({l,r})) L = l,R = r;
                else L = r,R = l;
            }
            else if (sure == 0) L = l,R = r;
            else L = r,R = l;
            break;
        }
        int mid = (l + r) >> 1;
        if (check (l,mid)) r = mid;
        else l = mid; 
    }
    ans[L] = '(';ans[R] = ')';
    vector <int> p;
    for (int i = 1;i <= n;++i)
        if (i != L && i != R) p.push_back (i);
    int pos = 0,sz = p.size ();
    while (pos < sz)
    {
        vector <int> nw (UP,L);
        for (int i = 0;i < UP;++i) 
            if (pos + i < sz) nw[i] = p[pos + i];
        vector <int> lst;
        for (int i = 0;i < UP;++i)
        {
            for (int j = 0;j < dig[i];++j) lst.push_back (L),lst.push_back (nw[i]);
            lst.push_back (L);
        }
        int res = query (lst);
        for (int i = UP - 1;~i;--i)
        {
            if (nw[i] == L) continue;
            if (res >= (1 + dig[i]) * dig[i] / 2) ans[nw[i]] = ')',res -= (1 + dig[i]) * dig[i] / 2;
            else ans[nw[i]] = '(';
        }
        pos += UP;
    }
    printf ("! ");
    for (int i = 1;i <= n;++i) printf ("%c",ans[i]);
    puts ("");fflush (stdout);
}
int main ()
{
    int t = read ();
    while (t--) solve ();
    return 0;
}
inline int read ()
{
    int s = 0;int f = 1;
    char ch = getchar ();
    while ((ch < '0' || ch > '9') && ch != EOF)
    {
        if (ch == '-') f = -1;
        ch = getchar ();
    }
    while (ch >= '0' && ch <= '9')
    {
        s = s * 10 + ch - '0';
        ch = getchar ();
    }
    return s * f;
}
相关推荐
Tisfy9 分钟前
LeetCode 2411.按位或最大的最小子数组长度:一次倒序遍历
数据结构·算法·leetcode·题解·位运算·遍历
2202_7567496936 分钟前
04 基于sklearn的机械学习-梯度下降(上)
人工智能·算法·机器学习
草莓爱芒果36 分钟前
Spring Boot中使用Bouncy Castle实现SM2国密算法(与前端JS加密交互)
java·spring boot·算法
晚云与城1 小时前
【数据结构】-----排序的艺术画卷
数据结构·算法·排序算法
weixin_307779131 小时前
设计Mock CUDA库的流程与实现
c++·算法·gpu算力
郝学胜-神的一滴1 小时前
应用Builder模式在C++中进行复杂对象构建
开发语言·c++·程序人生
j_xxx404_1 小时前
数据结构:算法复杂度与空间复杂度
c语言·数据结构·算法
dlraba8022 小时前
基于 OpenCV 与 sklearn 的数字识别:KNN 算法实践
opencv·算法·sklearn
归云鹤2 小时前
C++ 构造函数语义学
开发语言·c++
yzzzzzzzzzzzzzzzzz2 小时前
leetcode热题——全排列
算法·回溯·全排列