P8361 [SNOI2022] 倍增 题解

· · 题解

很厉害的构造题。下文所述中一个 B 进制数的第 i 位为从左往右数第 i 位。

考虑对于一个进制 B,若能构造出长度为 n 的答案,则一定能构造出长度为 n+1 的答案:因为若 2\times \overline{d_1d_2\cdots d_n}=\overline{d_{p_1}d_{p_2}\cdots d_{p_n}},那么一定有 2\times \overline{d_1d_2\cdots d_{i-1}(B-1)d_i\cdots d_n}=\overline{d_{p_1}d_{p_2}\cdots d_{p_{i-1}}(B-1)d_{p_i}\cdots d_{p_n}},其中 i 表示在长度为 n 的原数中某个向前一位进位的数位;即在第 i-1 位与第 i 位中插入了一个 B-1 必然也能满足条件。于是问题转换为:对于每个 B 找出最小的 n 使得 n 有解。

大胆猜测对于 2\le B\le 2\times 10^5,最小的满足条件的 n 不大——事实证明存在较少个 B 使得 n=9,其它情况下均满足 n\le 8。于是可以提出一个十分暴力的做法:

枚举原数 x=\overline{d_1d_2\cdots d_n} 的每一位对应 2x=\overline{d_{p_1}d_{p_2}\cdots d_{p_n}} 的哪一位(即确定一个排列 p),再枚举 2\sim n 中每一位有没有是否有向前一位进位,之后就可以得到关于 d_in 个方程——是可以解出所有 d_i 的。

具体地,对于一个排列 p,连边 i\to p_i,可以得到若干个环。每条边代表的方程是如下的形式:令 b_x 表示 x 是否向 x-1 进位,如果进位为 1 否则为 0,则方程为 d_{p_i}=2d_i+b_{i+1}-B\cdot b_i

对于一个环 c_1,c_2,\ldots,c_k,通过如下方法解出所有 d_{c_i} 的值:将 d_{c_2} 用一个关于 d_{c_1} 的一次函数表示,接着就可以表示 d_{c_3},d_{c_4},\ldots,d_{c_n},最后由于 c_n\to c_1 也有一个方程,所以能确定 k',b' 使得 d_{c_1}=k'd_{c_1}+b',得出 d_{c_1} 的值后又能顺次推出其他的 d_{c_i};最后检查一下所有解出的 d_i 是否满足 d_i\in[0,B)\cap\Z 即可。

如果你被卡常,下载数据后发现 B=32131 跑得最慢,打表一下就过了。

放代码:

#include<bits/stdc++.h>
using namespace std;
const int N=2e5;
vector<int> vl[N+1],vr[N+1];
pair<vector<int>,vector<int> > solve(int B){
  if(B==32131){
    return make_pair(
      vector<int>({7545,15090}),
      vector<int>({30181,28232,24334,3772,16537,943,1886})
    );
  } // 特判
  for(int n=2;;n++){
    vector<int> p(n);
    iota(p.begin(),p.end(),0);
    do{
      for(int S=2;S<1<<n;S+=2){
        bool f=true;
        vector<bool> b(n);
        vector<int> r(n,-1);
        for(int i=0;i<n&&f;i++)
          if(!b[i]){
            int x=i; vector<int> v;
            while(!b[x])v.emplace_back(x),b[x]=true,x=p[x];
            pair<int,int> lf(1,0); // 一次函数
            for(int i:v)lf=make_pair(lf.first*2,lf.second*2+(S>>i+1&1)-(S>>i&1)*B);
            if(lf.second%(lf.first-1))f=false; // 无整数解
            else{
              r[v[0]]=-lf.second/(lf.first-1);
              for(int i=1;i<v.size();i++)
                r[v[i]]=r[v[i-1]]*2+(S>>v[i-1]+1&1)-(S>>v[i-1]&1)*B;
            } // 顺次推出所有值
          }
        for(int i=0;i<n&&f;i++)
          f&=0<=r[i]&&r[i]<B;
        if(f){
          for(int i=0;i<n;i++)
            if(S>>i&1){
              vector<int> L,R;
              for(int j=0;j<i;j++)
                L.emplace_back(r[j]);
              for(int j=i;j<n;j++)
                R.emplace_back(r[j]);
              return make_pair(L,R);
            }
        }
      } // 枚举进位
    }while(next_permutation(p.begin(),p.end()));
    // 枚举排列
  }
}
int main(){
  ios::sync_with_stdio(false);
  cin.tie(0); cout.tie(0);
  int t; cin>>t;
  while(t--){
    int n,b; cin>>n>>b;
    if(vl[b].empty())tie(vl[b],vr[b])=solve(b);
    if(vl[b].size()+vr[b].size()>n){cout<<"-1\n"; continue;}
    for(int i:vl[b])cout<<i<<' ';
    for(int i=0;i<n-vl[b].size()-vr[b].size();i++)
      cout<<b-1<<' ';
    for(int i:vr[b])cout<<i<<' ';
    cout<<'\n';
  }
  return 0;
}