题解:CF2096G Wonderful Guessing Game

· · 题解

分析

我们先不考虑必须先提出所有查询和忽略一个询问这两个条件。

这样一来,题目和小学数学中的找假币问题十分相似。

因此我们可以用类似的做法做:

这样子总共 \lceil \log_3 n\rceil 次查询,可以证明这是最小查询次数。

接下来我们考虑加入必须先提出所有查询这个条件。

我们可以根据上面解法,先建出的决策树。每个结点是一个区间,对于结点 x 的区间划分出的 0 号区间,新建一个代表该区间的结点并向它连一条边权为 0 的边,对于结点 x 的区间划分出的 1 号区间,新建一个代表该区间的结点并向它连一条边权为 1 的边,以此类推。

当一个结点区间长度为 1 却没到最大深度(即 \lceil \log_3 n\rceil)时,钦定它有一个 2 号区间,这个区间就是它自己。

每一次询问,将每一层的所有 0 区间结点合在一起,12 号区间也是一样。然后对于每一层,将结合后的 0 区间和结合后的 1 区间拼在一起,询问这个东西。如果是结果为 L 则在 0 号区间,为 R 则在 1 号区间,为 N 则在 2​ 号区间。

也就是说,每一层的询问结果相当于一个“跳结点指令”,当跳到该层的结点时,为 L 则跳到这个结点的 0 号区间,为 R 跳到这个结点的 1 号区间,为 N 跳到这个结点的 2 号区间。

在决策树上,按照指令从根往下跳就能知道答案。

最后我们考虑忽略一个查询的条件,这时要再加一个查询,以便能够还原被忽略的查询。

考虑对于每个叶子结点,将根结点到每个叶子结点的边编号加起来对 3 取模。

如果这个模数为 x,钦定这个结点放在这次询问的 x​​ 号区间。

可以注意到,三种编号有两种数量相同,将数量相同的两个编号映射到 0,1 号区间,另一个映射到 2 号区间,对映射后的查询完再映射回来即可。接着对映射后 01 两个区间拼起来进行查询,结果为 L 则在 0 号区间,为 R 则在 1 号区间,为 N 则在 2 号区间。

下面需要证明三种编号有两种数量相同。如证明无法理解可跳过。

证明

f_{i,j} 表示 n 个数的决策树,根结点到每个叶子结点的边编号加起来模 3j 的数个数。则此命题即为:对于任意正整数 nf_{n,0},f_{n,1},f_{n,2} 有两个相等。

(因为 n>1,为方便,n=1 时决策树最大深度为 2 而不是 1。)

为证明该命题,需要证明一个显然更强的命题:在原命题的基础上,如果 n \bmod 3=0,则 f_{n,0}=f_{n,1}=f_{n,2},否则如果 n \bmod 3=2f_{n,0}+f_{n-1,0}=f_{n,1}+f_{n-1,1}=f_{n,2}+f_{n-1,2}

使用数学归纳法。

n=1,2 时已成立。当 n\ge 3 时,假设 1n-1 时已成立,则有:

n \bmod 3=0

我们有:

\begin{cases} f_{n,0}=f_{\lfloor \frac{n}3 \rfloor,0}+f_{\lfloor \frac{n}3 \rfloor,2}+f_{\lfloor \frac{n}3 \rfloor,1} \\ f_{n,1}=f_{\lfloor \frac{n}3 \rfloor,1}+f_{\lfloor \frac{n}3 \rfloor,0}+f_{\lfloor \frac{n}3 \rfloor,2} \\ f_{n,2}=f_{\lfloor \frac{n}3 \rfloor,2}+f_{\lfloor \frac{n}3 \rfloor,1}+f_{\lfloor \frac{n}3 \rfloor,0} \end{cases}

f_{n,0}=f_{n,1}=f_{n,2},命题成立。

n \bmod 3=2

我们有:

\begin{cases} f_{n,0}=f_{\lfloor \frac{n}3 \rfloor+1,0}+f_{\lfloor \frac{n}3 \rfloor+1,2}+f_{\lfloor \frac{n}3 \rfloor,1}\\ f_{n,1}=f_{\lfloor \frac{n}3 \rfloor+1,1}+f_{\lfloor \frac{n}3 \rfloor+1,0}+f_{\lfloor \frac{n}3 \rfloor,2}\\ f_{n,2}=f_{\lfloor \frac{n}3 \rfloor+1,2}+f_{\lfloor \frac{n}3 \rfloor+1,1}+f_{\lfloor \frac{n}3 \rfloor,0} \end{cases}

且有:

\begin{cases} f_{n-1,0}=f_{\lfloor \frac{n}3 \rfloor,0}+f_{\lfloor \frac{n}3 \rfloor,2}+f_{\lfloor \frac{n}3 \rfloor+1,1} \\ f_{n-1,1}=f_{\lfloor \frac{n}3 \rfloor,1}+f_{\lfloor \frac{n}3 \rfloor,0}+f_{\lfloor \frac{n}3 \rfloor+1,2} \\ f_{n-1,2}=f_{\lfloor \frac{n}3 \rfloor,2}+f_{\lfloor \frac{n}3 \rfloor,1}+f_{\lfloor \frac{n}3 \rfloor+1,0} \\ \end{cases}

不妨将 f_{n,0},f_{n,1},f_{n,2} 同时减去一个数,f_{n-1,0},f_{n-1,1},f_{n-1,2} 同时减去一个数。

由式子的对称性,如果 \lfloor \frac{n}3\rfloor+1 \bmod 3=2 则不妨设:

\begin{cases} f_{\lfloor \frac{n}3 \rfloor,0}=f_{\lfloor \frac{n}3 \rfloor,1}=0,f_{\lfloor \frac{n}3 \rfloor,2}=d \\ f_{\lfloor \frac{n}3 \rfloor+1,0}=f_{\lfloor \frac{n}3 \rfloor+1,1}=0,f_{\lfloor \frac{n}3 \rfloor+1,2}=-d \end{cases}

f_{n,0}=f_{n,2}=-d,f_{n,2}=df_{n,0}=f_{n,2}=d,f_{n,2}=-df_{n,0}+f_{n-1,0}=f_{n,1}+f_{n-1,1}=f_{n,2}+f_{n-1,2}=0,命题成立。

如果 \lfloor \frac{n}3\rfloor+1 \bmod 3=1 则不妨设:

\begin{cases} f_{\lfloor \frac{n}3 \rfloor,0}=f_{\lfloor \frac{n}3 \rfloor,1}=0,f_{\lfloor \frac{n}3 \rfloor,2}=0 \\ f_{\lfloor \frac{n}3 \rfloor+1,0}=f_{\lfloor \frac{n}3\rfloor+1,1}=0,f_{\lfloor \frac{n}3 \rfloor+1,2}=d \end{cases} 如果 $\lfloor \frac{n}3\rfloor+1 \bmod 3=0$,此情况与$\lfloor \frac{n}3\rfloor+1 \bmod 3=1$ 对称,命题成立。 因此,更强的命题成立,原命题也成立。 ## 代码 ```cpp #include<bits/stdc++.h> #define int long long #define __builtin_popcount __builtin_popcountll using namespace std; const int N=200005,M=25; int n,m,q,cnt,a[M][N],w[N],cw[M],z; struct node{ int l,r,son[3]; }tree[N<<2]; void build(int x,int l,int r,int d,int e){ tree[x].l=l; tree[x].r=r; for(int i=l;i<=r;i++){ a[d][i]=e; } if(d==m)return; int len=r-l+1; if(len==1){ tree[x].son[2]=++cnt; build(tree[x].son[2],l,l,d+1,2); return; } if(len==2){ tree[x].son[0]=++cnt; tree[x].son[1]=++cnt; build(tree[x].son[0],l,l,d+1,0); build(tree[x].son[1],r,r,d+1,1); return; } for(int i=0;i<3;i++)tree[x].son[i]=++cnt; switch(len%3){ case 0:{ int w=len/3; build(tree[x].son[0],l,l+w-1,d+1,0); build(tree[x].son[1],l+w,r-w,d+1,1); build(tree[x].son[2],r-w+1,r,d+1,2); break; } case 1:{ int w=len/3; build(tree[x].son[0],l,l+w-1,d+1,0); build(tree[x].son[1],l+w,l+w+w-1,d+1,1); build(tree[x].son[2],l+w+w,r,d+1,2); break; } case 2:{ int w=len/3; build(tree[x].son[0],l,l+w,d+1,0); build(tree[x].son[1],l+w+1,l+w+w+1,d+1,1); build(tree[x].son[2],l+w+w+2,r,d+1,2); break; } } } inline int tonum(char c){ if(c=='L')return 0; else if(c=='R')return 1; else return 2; } inline int skip(string&s){ int x=1; for(int i=1;i<=m;i++){ x=tree[x].son[tonum(s[i])]; } return tree[x].l; } inline void solve(){ cin>>n; cnt=1,m=0; for(int i=1;i<=20;i++)for(int j=1;j<=n;j++)a[i][j]=w[j]=0; cw[0]=cw[1]=cw[2]=0; for(int p=1;p<n;p*=3)++m; build(1,1,n,0,0); cout<<m+1<<endl; for(int i=1;i<=m;i++){ int c=0; for(int j=1;j<=n;j++)if(a[i][j]!=2)++c; cout<<c<<' '; for(int j=1;j<=n;j++)if(a[i][j]==0)cout<<j<<' '; for(int j=1;j<=n;j++)if(a[i][j]==1)cout<<j<<' '; for(int j=1;j<=n;j++)(w[j]+=a[i][j])%=3; cout<<endl; } for(int i=1;i<=n;i++)++cw[w[i]]; if(cw[0]==cw[1])z=0; else if(cw[0]==cw[2])z=1; else z=2; for(int i=1;i<=n;i++)(w[i]+=z)%=3; int c=0; for(int j=1;j<=n;j++)if(w[j]!=2)++c; cout<<c<<' '; for(int j=1;j<=n;j++)if(w[j]==0)cout<<j<<' '; for(int j=1;j<=n;j++)if(w[j]==1)cout<<j<<' '; cout<<endl; string s; cin>>s; s=' '+s; int f=0,sum=0,sum2=0; for(int i=1;i<=m;i++){ if(s[i]=='?'){ f=i; }else{ (sum2+=tonum(s[i]))%=3; } } if(f){ sum=(tonum(s[m+1])+3-z)%3; s[f]="LRN"[(sum+3-sum2)%3]; } cout<<skip(s)<<'\n'; } signed main(){ cin>>q; while(q--)solve(); return 0; } ```