概要

  • N(=3000)枚の寿司ネタ(a-zの26種類)を連続して流す(文字列Sとする)。
  • その中からM(=7)枚の部分列がQ(=30000)個問い合わせられる。ただし場合により部分列は連続していないことがある。
  • そのそれぞれに対し、S上でのインデックスを答える。

決定的アルゴリズム

この問題、D=1とすれば、部分列は連続であることが保証されます。つまり、決定的アルゴリズムを使うことができます。

  • N枚のうちで連続したM枚についてすべて異なるようにSを生成します。
    • 完全に決定的にするには https://en.wikipedia.org/wiki/De_Bruijn_sequence とか使うんですが、発展のために少しでもエントロピーを上げたいので、この部分は乱択を使って良いと思います。
  • すべて異なることがわかっているのだから、M枚の部分文字列を単にSから探すだけで終わりです。
  • Sの生成さえできれば、1ケースに付き30000点、 合計300000点を確実に獲得できます。

  • Rubyの事実上初版 https://rco-contest-2018-final-open.contest.atcoder.jp/submissions/2177816

発展

  • この状態であれば、0..Nのそれぞれ(j)に対し、qがS[j,M*3]中にこの順番で入っている数(ただしSの先頭に入っているほど重みを大きくする)が最大となるjをインデックスとして答える実装にすることで、D=2.5程度までは安定してインデックスを求めることができます。
    • 「*3」は、元となる部分文字列の長さはM*3以下であることを期待しています。
  • この「重み」が評価関数の肝になると思うのですが、調整の余地が残っている気がします。

  • C++の(コンテスト後)最終版(381792点) https://rco-contest-2018-final-open.contest.atcoder.jp/submissions/2179945

  • なお、コンテスト中は元となる部分文字列の長さがMであり、qの長さがM未満になりうると誤読していました、D=1.5にしかできなかったので324730点どまりでした。無念。

記事としての結論

  • 順位表で、ある点数を取っている人が沢山いる場合は決定的アルゴリズムを疑うのは有用ではないかと思います。

補足

  • Sの一例は次のとおりです:
1
kzsbqmuxtqwazgonugucjaouqsihsmgjpvdiwscfaewltvsyolgoenjcmlylyzhqlhgaypshugqlrqfxjigsliujfconzsyrnctoeztichmtznzxfhfkcpytpenkyqxmwxeaihahytbkbqkzxplhdtnzbsnpwhdpzjykrgwoxihvkiqrgmruztplaiuqecvbrvlbkwsbylxgxytyhsyiedtfvkjiylnjmcmhvtlkrmqbafrhngsqrvfbkadbkjshyhbagjoyhqmwipzegzqjkpxoquezkdplxombjqkdtbgnghkdaregkmrxelhpfbfoewbnfewmqvyivwkhfahoryxqvqnoguwpcldzcqlbpfruijrenfofeajcnrtcnqlzhbwdxwglmbkhdfhdyhfinxkwksypafghuoilqofnmbiavcqtjszdtowlqsdienoymvlrhczlysmlokhbvluaelotziotdtcwnvsigjnfjfywkeyalcmxgwyojkjrigvlnoqehmgopgvhntqxvnmxobebiwbzomjracdoerdndvbdygoyeayujwhtoiouxhmdbqusqbmqwnucgbzligimgnaswqbcdjchomvihdwmksltueulypyjsrnjeyoipdfxtveurkuwxatwmawmvrahsgexunjrdzemesmkfxmdzjvqxijdqoabvahbzeklborztczqlicyzbpemqinofcfuvaznakgxaevhcmqgmnescpxghixypxsxcxcqsqpkldvycwkxhkqcwqlqxnpoazsohnoucsztqdizbntfdeteiximtuymuzodlquigwhzoqjezyqahpkifvbyhkibmhslzdwybpojuegpyopwcdigpepmgpgdgdlumhrnfscleimrhriyjxfypdawcybayotfguipljmjvzxixpxugnlzvqemwluzwskegvpmpvrftyrfegxwetstcouotmofsakluqdmliroqiaodhrbvxnqgwrqntenstlhykhjalzlwuqvcmwawfyqojhpymlgycsxyhgqxjvesdbifgxitdikicrtnuzywysuvxrsidvtwisowtvltfyvmeqdpnzhklcvxmygjzmtpfqzezwkgiwfdctpchqfljqenqbxhxuivldilsldfesvtebuxcpqsharzikqnvxudumxqlknkpblcicqjimafnaqbocjmguzcliduealstwhnyqcbypexcnyjndbwgvefjwlbzilcqejkylrwqmbzsxolbvuihmkvyjlwkitvthxsmozyektmnhxifxziytweudfnhipwstdzwqcekmuswolxrvwagpfhlgwqhqsgocwioykxpnlkpcjtzdfwfxnfrdjnpkmfkninbsmpdblqveasiejnqxthdioxfpruxabcjftnkgdphravwvzlcgtonqcftlckmvmxvhojwuzpetwyhzvcrgclxembtiksgkxrnwhpamnrcztbftknzijgsrxwnfxrzhacvfakjapavrbrmdacqhurnqozroebwnqjgmcnpdwkrhzwbrusawknwyglczmewansanujgkljlntcbxeqtuczwnsvawpgkzkzqnruqbnuoepfxylqmqfodorsrlyuhoxtgxqizpzokjqskcapuoctghnhsprjgyayidqelwfaufxvpcqgtbvgltgbrbmtawovsdvsnmjymqdtkjeptmhmcjenmwktbnvmqnjyuceclnpeqktpdhclyvwbszlqlvzbahuktlwyzphaoaftshmvelbhvbqyfatbjmknybdnpjpxtovwodvxzdapgwbekptxputuqwsfrcqyauvegdfcjsjudqnqiqsfzeoevlkdczpkwejcfekodqdcpzqsjfhgkovcjzhtnyosceiknalxljzpcsnqnpvigrmblkflihbrireolvlpmyunynuemvfkmyhryqvwcqrbnryjydnsusbesncslszxjpirhqxbfnsxlwsrbxurvjystorpmeiyxcmgynknfdlzqgatiyhmyfksjbcokchpbgxgjkiafsmzepbfmjzyntzcalnfufqlgqkwynmizavocyqijpuevsjdlvbwmycvepjzvhxznxlyhaeypicgiyfnxwasyukgmiwikmgtkvgwevafmkpnphuvczrjcgqewivjugpikduiofvaeshowxzcuxecemurqieiflobzxbxswciepxwfmhkevjxtngfxgckfvtztjyznqkyiymeauyvkdgspmzgxmxuoztrkpudrdowrysdcozcpbydsygxroclwhldqrzswnldbgjybwhrseljwmclgzabgpmthltqocahrzrnowgpcrzkazcyhxqdknlapzhjyhjsnejbnoeabjvdcxjkgcniokyxyjujkfmtgrxdvwpnbxficmzrbspvozlsmdoxdefbhgmfumuakdxdivrvzjfdngjqxutjeibzahvmktqjvryplivblnetnvukxjmvtgzidxhokfhicoaqvhpmwpoefwgqsznyczfetgnfqtwornmnslycqodunanvrzvtufmxdqubnqfbyzrwcmyteukdskmjhsijkhzqopckiobdabsyzujbxjnymiaroiwafpwbpihqnhlmxsopoxbqrfxhavgqurfpkgskntoavqdbfhqkfyspwmihrtesoemfnbwswyqutsaujcdkepdukawaigywzveizimplsnvalkgrgeuznvbxgeruhwoijvmcazucrvofdmvuqcsyejrmeyclsgafckhcdtpvlvymcvswgfzkphxbmgcdxzlxzqmdwocowclvwrspkurzjsbnxfxiatubjfriasfyxwdejpmsfvmfptijwykizxoubgetmbsogpwqwkqlsfxklopxpwjwrpizgqonplrpfpmqagnmcgcoqgemovxtxrkdnwqugvmyqetbiqchfcmpsunwjnuplmzwjplfj

まず、最下位が0,6,7の場合はNoです。後述する状態1/0への移行は十の位以降で発生する必要があります。in06がこのケースです。

それ以外の場合。 下の桁から順番に見ていきます。3つの状態があるので適切に管理します。

  • 状態2(私のソースコードに生えた数字ですが、まあ「2つのラッキーナンバーが影響している」とでも考えて下さい)のとき、2,3,4,0,6,7を受け付けます。
    • 2,3,4の場合は、繰り上がりがあるので、上の桁から1を引きます。
      • すでに最上位の場合はNoです。
      • (最上位でないが上位桁が0の場合、次の桁で弾かれるため、無理やり引いても通ります。)
    • 6,7の場合は、状態1に移行します。
    • 0の場合は、状態0に移行します。
  • 状態1のとき、0,6,7を受け付けます。
    • 0の場合は、状態0に移行します。
  • 状態0のとき、0のみを受け付けます。
    • ラッキーナンバーが影響し得ないということは、すべての桁が0になっている以外にないです。
    • (間に合わなかった)チャレンジでは、612だと、下位の12で6+6と完結しているのに、新たな600が出てきているので、十の位が不合理となるのです。

https://yukicoder.me/submissions/238472

https://yukicoder.me/problems/no/649

配列の先頭要素からの削除はO(配列長)ですが、dequeを使うと先頭からの削除は速くなります。 中央からの削除はやはり遅いですが、定数倍の高速化が望めるので、時間制限の設定によっては間に合う可能性があります。

なお、マスと駒と色塗りで、範囲管理をvectorでなくdequeでやると通ってしまいます(setでやるのが最善ではあります)。 - http://cielavenir.github.io/blog/2015/10/29/many-ameba/

https://yukicoder.me/problems/no/648

基本的にはむこさんの解説のとおり、(sqrt(8*n+1)-1)/2を出力すれば良いですが、実装上の別解を。

  1. Integer sqrtの利用
  2. sqrtlの利用
    • long doubleの仮数部が64bitであることを思い出せば、sqrtlを使えば解けることは想像に固くありません。1
    • C https://yukicoder.me/submissions/206063
    • Python (ctypes) https://yukicoder.me/submissions/207816
      • intをマーシャリングするときにfloatを経由されるといけないので、sscanfを用いて自力でマーシャリングする必要があります

  1. あれ、topcoder初出場で撃沈したのは誰だ^^; (SRM 635 Div2 Medやらyukicoder No.413やら)

https://yukicoder.me/problems/no/580

  • 区間スケジューリング問題の、スロットが複数あるバージョンである。
  • 基本的な解き方は同じだが、popするスロットは、「部屋iの(出)時刻がdata[i]の入時刻より早い中で最大」のものである。
    • このスロットを検索するには、multisetを2分探索する以上に効率のよい方法はなさそうである。
      • 勿論普通の区間スケジューリングではこの集合は1個しか無いため定数時間である。
    • 私は最初、最小のものがdata[i]の入時刻より早ければpopする実装にして盛大に間違いました…
  • スロットを線形探索しないため、計算量をnからlognに落とすことができ、合わせてO(m(logm+logn))となる(実際にはn<mだと思われるのでO(mlogm))。

  • https://yukicoder.me/submissions/211209 (現状の最短時間とのこと)

  • n<=10000, m<=100000なるHardバージョンを作ろうと思いましたが必要ない気もするのでやめておきます。。

http://arc082.contest.atcoder.jp/tasks/arc082_c

初版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <complex>
#include <vector>
#include <set>
#include <cstdio>
using namespace std;

const double EPS = 1e-12;
typedef complex<long double> P;

struct L : public vector<P> {
  L(const P &a, const P &b) {
    push_back(a); push_back(b);
  }
};
bool intersectSP(const L &s, const P &p) {
  return abs(s[0]-p)+abs(s[1]-p)-abs(s[1]-s[0]) < EPS; // triangle inequality
}

long long pow_binary_mod(long long x,long long y,long long M){
  long long z=1;
  for(;y;y>>=1){
      if((y&1)!=0)z=z*x%M;
      x=x*x%M;
  }
  return z;
}

int main(){
  int M=998244353;
  int N,x,y;
  scanf("%d",&N);
  vector<P>v(N);
  int r=pow_binary_mod(2,N,M);
  set<L>se;
  for(int i=0;i<N;i++)scanf("%d%d",&x,&y),v[i]={(double)x,(double)y};
  for(int i=0;i<N;i++)for(int j=i+1;j<N;j++){
      L l(v[i],v[j]);
      int n=0;
      for(int k=0;k<N;k++)if(k!=i&&k!=j&&intersectSP(l,v[k]))n++;
      r=(r-pow_binary_mod(2,n,M))%M;
  }
  printf("%d\n",((r-N-1)%M+M)%M);
}

点が載っている判定を直線へ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <complex>
#include <vector>
#include <set>
#include <cstdio>
using namespace std;

const double EPS = 1e-12;
typedef complex<double> P;

double cross(const P& a, const P& b) {
  return imag(conj(a)*b);
}

struct L : public vector<P> {
  L(const P &a, const P &b) {
    push_back(a); push_back(b);
  }
};

bool intersectLP(const L &l, const P &p) {
  return abs(cross(l[1]-p, l[0]-p)) < EPS;
}

long long pow_binary_mod(long long x,long long y,long long M){
  long long z=1;
  for(;y;y>>=1){
      if((y&1)!=0)z=z*x%M;
      x=x*x%M;
  }
  return z;
}

int main(){
  int M=998244353;
  int N,x,y;
  scanf("%d",&N);
  vector<P>v(N);
  int r=pow_binary_mod(2,N,M);
  set<pair<int,int> >se;
  for(int i=0;i<N;i++)scanf("%d%d",&x,&y),v[i]={(double)x,(double)y};
  for(int i=0;i<N;i++)for(int j=i+1;j<N;j++){
      if(se.find({i,j})!=se.end())continue;
      L l(v[i],v[j]);
      vector<int>x={i,j};
      for(int k=0;k<N;k++)if(k!=i&&k!=j&&intersectLP(l,v[k]))x.push_back(k);
      for(int a=0;a<x.size();a++)for(int b=a+1;b<x.size();b++){
          int n=b-a-1;
          r=(r-pow_binary_mod(2,n,M))%M;
          se.emplace(x[a],x[b]);
      }
  }
  printf("%d\n",((r-N-1)%M+M)%M);
}

インライン化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <complex>
#include <vector>
#include <set>
#include <cstdio>
using namespace std;

const double EPS = 1e-12;
typedef complex<double> P;

long long pow_binary_mod(long long x,long long y,long long M){
  long long z=1;
  for(;y;y>>=1){
      if((y&1)!=0)z=z*x%M;
      x=x*x%M;
  }
  return z;
}

int main(){
  int M=998244353;
  int N,x,y;
  scanf("%d",&N);
  vector<P>v(N);
  int r=pow_binary_mod(2,N,M);
  set<pair<int,int>>se;
  for(int i=0;i<N;i++)scanf("%d%d",&x,&y),v[i]={(double)x,(double)y};
  for(int i=0;i<N;i++)for(int j=i+1;j<N;j++){
      if(se.find({i,j})!=se.end())continue;
      vector<int>x={i,j};
      for(int k=j+1;k<N;k++)if(abs(imag(conj(v[j]-v[k])*(v[i]-v[k])))<1e-12)x.push_back(k);
      for(int a=0;a<x.size();a++)for(int b=a+1;b<x.size();b++){
          r=(r-pow_binary_mod(2,b+~a,M))%M;
          se.emplace(x[a],x[b]);
      }
  }
  printf("%d\n",((r-N-1)%M+M)%M);
}

Ruby

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/ruby
M=998244353
N=gets.to_i
v=$<.map{|e|Complex(*e.split.map(&:to_i))}
h={}
r=2**N%M-N-1
N.times{|i|(i+1...N).map{|j|
  h[x=[i,j]]||
  (j+1...N).map{|k|((v[j]-v[k]).conj*(v[i]-v[k])).imag.abs<1e-9&&x<<k}&&
  (l=x.size).times{|a|(a+1...l).map{|b|
      r=(r-2**(b+~a))%M
      h[[x[a],x[b]]]=1
  }}
}}
p r

最終版

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/ruby
M=998244353
N=gets.to_i
v=$<.map{|e|Complex(*e.split.map(&:to_i))}
h={}
r=2**N+~N
[*0...N].combination(2){|i,j|
  h[x=[i,j]]||(
  (j+1...N).map{|k|((v[j]-v[k]).conj*(v[i]-v[k])).imag.abs<1e-9&&x<<k};
  r-=2**(l=x.size)+~l;
  x.combination(2){|a,b|h[[a,b]]=1})
}
p r

https://yukicoder.me/problems/no/550

  • 解と係数の関係から3因数はCの約数である.
  • https://gist.github.com/pekempey/9eddf9342f65552a92845e035960e8a3 によると約数の個数は最大で103680なので、約数の全列挙をすればこの問題を解くには十分である.
  • なお, Cの最大値は10**18であるが, 因数の最大値は10*9であるという制約があるので, Ruby標準ライブラリの素因数分解で問題ない.

    • -1999999865 999999864000004607 999999866000004473という入力(出力は-1 999999929 999999937)で死ぬので, 素因数列挙だけで解きたい場合はロー法を使いましょう…
  • Ruby標準ライブラリ(リジャッジでTLEになる予定) https://yukicoder.me/submissions/192779

  • ロー法 https://yukicoder.me/submissions/192783

背景

  • ファミリーマートやローソンのネットワークプリントはプリンタドライバが存在するが、セブンイレブンのネットプリントはプリンタドライバが存在しない。
  • ECCS2012(ゼロックス)のプリンタドライバはネットプリントドライバとしても使うことが出来た。
  • 現在のECCS2016では業者がリコーになったが、ネットプリントドライバ部分はまだ使えるのではないか?
  • しかし、使ってみようとすると、接続エラー(320000)となる。

調査

  • /Library/Printers/FujiXerox/FXCampusGatePrinter/FXCampusGatePrinter.plist から、
  • https://sdb.fujixerox.co.jp:443/CGPPrintPortal/Service.svc
  • という内容が読み取れる。このサーバーは現在停止している。
  • したがって、ネットプリントドライバとして使うことも現在は不可能である。

結論

  • 個人的には当面はネットワークプリントだけを使うことにします…

https://agc009.contest.atcoder.jp/tasks/agc009_b

概要

  • トーナメント戦を行ったところ、1番の選手が優勝した。
  • 他の選手について、誰に負けたのかが与えられるので、条件をみたすようなトーナメント表の深さの最低値を求めよ。

考察

  • ある選手がどの選手を倒したかのグラフを作成し、1番の選手から再帰。
  • 倒した選手(N人)のそれぞれについてトーナメント表の深さを求めようとする(倒した選手がいなければ0)。
  • 深さを降順にソートし、これらに1からNまでの数値を割り当てて、足す。
  • 足した後の配列の最大値が答えである。

再帰の深さ

  • 単純に実装すると、最悪10万回の再帰が必要になり、スタックオーバーフローになるおそれがある。
  • 今回、C++は 偶然 大丈夫であったが、RubyやPythonだと不可であった。
    • スタック拡張テクを使っても依然として通らない。
  • 今回は場当たり的(テストケース依存)な対応になるが、N/2番の選手から一旦再帰しておくことで再帰の深さをある程度減らすことができる。
    • この状態でもスタック拡張テクは依然として必要である。

解答例

余談