CF1970D2 Arithmancy (Medium) 题解

· · 题解

点这里前往游记。

场切了这个 \color{Red}*2600,太舒服了。自从来了某学校,卡时、假算、乱搞能力显著增强;不过这题乱搞似乎就是真算。

建议先阅读 D1 题解。

我们沿用 D1 的做法,考虑随机一堆字符串然后打表。但是因为 n 比较大,所以 f(w_i+w_j) 有极大的概率会重复一些。由于这题 n\le 30,下文若无特殊说明则将 n 视为 30

但是题面有说,交互库的询问方式是每次均匀随机选择两个下标 (i,j)(1\le i,j\le n) 问。于是可以得出,只要重复的 (i,j) 的数量不太多,1000 次询问是可以过去的。

考虑均匀随机地生成那些字符串。这样直接干肯定是过不去的,于是我们考虑加入如下的若干技巧,使得随机可以通过题目:

自此我们用乱搞切掉了一道 \color{Red}\mathrm{*2600}

代码里面的 AtCoder Library 部分请自行补全,或使用 AtCoder Custom Test 测试样例。

放代码(GNU C++17, with AtCoder Library):

#include<bits/stdc++.h>
#include<atcoder/string>
#define int long long
using namespace std;
mt19937 g(time(0));
int lst; inline bool get(){
  if(g()%5<=3)return lst;
  return lst=!lst;
} // 特殊的生成方式——3/5 的概率继承上一个
inline int f(string s){
  int n=s.length(),c=n*(n+1)>>1;
  for(int i:atcoder::lcp_array(s,atcoder::suffix_array(s)))c-=i;
  return c;
} // 计算本质不同的子串个数
main(){
  ios::sync_with_stdio(false);
  int n,bs=0,st=clock(); cin>>n;
  vector<string> v;
  map<int,pair<int,int> > mp;
  mt19937 g(time(0));
  uniform_int_distribution<> l(n*15,n*30);
  while(1.0*(clock()-st)/CLOCKS_PER_SEC<4.8){
    vector<string> a(n);
    for(int j=0;j<n;j++){
      int x=l(g);
      for(int k=0;k<x;k++)
        a[j]+=(get()?'X':'O');
    } // 随机生成字符串
    vector c(n,vector<int>(n));
    set<int> s;
    for(int i=0;i<n;i++)
      for(int j=0;j<n;j++)
        s.emplace(c[i][j]=f(a[i]+a[j]));
    // 计算种类数,如果种类数大于当前最优解就更新
    if(s.size()>bs){
      bs=s.size(),v=a;
      for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
          mp[c[i][j]]=make_pair(i+1,j+1);
    } // 打表
  } // 卡时进行贪心
  for(int i=0;i<n;i++){
    cout<<v[i];
    if(i<n-1)cout<<'\n';
    else cout<<endl;
  } // 给出方案
  int q; cin>>q;
  while(q--){
    int x; cin>>x;
    auto [a,b]=mp[x];
    cout<<a<<' '<<b<<endl;
  } // 回答询问
  return 0;
}