#ifdef DEBUG #define _GLIBCXX_DEBUG #define DEXPR( LL , BOUND , VALUE , DEBUG_VALUE ) CEXPR( LL , BOUND , DEBUG_VALUE ) #define CERR( ANSWER ) cerr << ANSWER << endl; #define LIBRARY_SEARCH if( LibrarySearch() != 0 ){ QUIT; }; #else #pragma GCC optimize ( "O3" ) #pragma GCC optimize( "unroll-loops" ) #pragma GCC target ( "sse4.2,fma,avx2,popcnt,lzcnt,bmi2" ) #define DEXPR( LL , BOUND , VALUE , DEBUG_VALUE ) CEXPR( LL , BOUND , VALUE ) #define CERR( ANSWER ) #define LIBRARY_SEARCH #endif #include using namespace std; using uint = unsigned int; using ll = long long; using ull = unsigned long long; #define ATT __attribute__( ( target( "sse4.2,fma,avx2,popcnt,lzcnt,bmi2" ) ) ) #define TYPE_OF( VAR ) decay_t #define UNTIE ios_base::sync_with_stdio( false ); cin.tie( nullptr ) #define CEXPR( LL , BOUND , VALUE ) constexpr LL BOUND = VALUE #define CIN( LL , A ) LL A; cin >> A #define ASSERT( A , MIN , MAX ) assert( ( MIN ) <= A && A <= ( MAX ) ) #define CIN_ASSERT( A , MIN , MAX ) CIN( TYPE_OF( MAX ) , A ); ASSERT( A , MIN , MAX ) #define SET_ASSERT( A , MIN , MAX ) cin >> A; ASSERT( A , MIN , MAX ) #define GETLINE( A ) string A; getline( cin , A ) #define GETLINE_SEPARATE( A , SEPARATOR ) string A; getline( cin , A , SEPARATOR ) #define FOR( VAR , INITIAL , FINAL_PLUS_ONE ) for( TYPE_OF( FINAL_PLUS_ONE ) VAR = INITIAL ; VAR < FINAL_PLUS_ONE ; VAR ++ ) #define FOREQ( VAR , INITIAL , FINAL ) for( TYPE_OF( FINAL ) VAR = INITIAL ; VAR <= FINAL ; VAR ++ ) #define FOREQINV( VAR , INITIAL , FINAL ) for( TYPE_OF( INITIAL ) VAR = INITIAL ; VAR >= FINAL ; VAR -- ) #define FOR_ITR( ARRAY , ITR , END ) for( auto ITR = ARRAY .begin() , END = ARRAY .end() ; ITR != END ; ITR ++ ) #define REPEAT( HOW_MANY_TIMES ) FOR( VARIABLE_FOR_REPEAT , 0 , HOW_MANY_TIMES ) #define QUIT return 0 #define COUT( ANSWER ) cout << ( ANSWER ) << "\n" #define RETURN( ANSWER ) COUT( ANSWER ); QUIT #define SET_PRECISION( PRECISION ) cout << fixed << setprecision( PRECISION ) #define DOUBLE( PRECISION , ANSWER ) SET_PRECISION << ( ANSWER ) << "\n"; QUIT template inline T Absolute( const T& a ){ return a > 0 ? a : -a; } template inline T Residue( const T& a , const T& p ){ return a >= 0 ? a % p : ( a % p ) + p; } #define POWER( ANSWER , ARGUMENT , EXPONENT ) \ static_assert( ! is_same::value && ! is_same::value ); \ TYPE_OF( ARGUMENT ) ANSWER{ 1 }; \ { \ TYPE_OF( ARGUMENT ) ARGUMENT_FOR_SQUARE_FOR_POWER = ( ARGUMENT ); \ TYPE_OF( EXPONENT ) EXPONENT_FOR_SQUARE_FOR_POWER = ( EXPONENT ); \ while( EXPONENT_FOR_SQUARE_FOR_POWER != 0 ){ \ if( EXPONENT_FOR_SQUARE_FOR_POWER % 2 == 1 ){ \ ANSWER *= ARGUMENT_FOR_SQUARE_FOR_POWER; \ } \ ARGUMENT_FOR_SQUARE_FOR_POWER *= ARGUMENT_FOR_SQUARE_FOR_POWER; \ EXPONENT_FOR_SQUARE_FOR_POWER /= 2; \ } \ } \ #define POWER_MOD( ANSWER , ARGUMENT , EXPONENT , MODULO ) \ ll ANSWER{ 1 }; \ { \ ll ARGUMENT_FOR_SQUARE_FOR_POWER = ( MODULO + ( ( ARGUMENT ) % MODULO ) ) % MODULO; \ TYPE_OF( EXPONENT ) EXPONENT_FOR_SQUARE_FOR_POWER = ( EXPONENT ); \ while( EXPONENT_FOR_SQUARE_FOR_POWER != 0 ){ \ if( EXPONENT_FOR_SQUARE_FOR_POWER % 2 == 1 ){ \ ANSWER = ( ANSWER * ARGUMENT_FOR_SQUARE_FOR_POWER ) % MODULO; \ } \ ARGUMENT_FOR_SQUARE_FOR_POWER = ( ARGUMENT_FOR_SQUARE_FOR_POWER * ARGUMENT_FOR_SQUARE_FOR_POWER ) % MODULO; \ EXPONENT_FOR_SQUARE_FOR_POWER /= 2; \ } \ } \ #define FACTORIAL_MOD( ANSWER , ANSWER_INV , INVERSE , MAX_INDEX , CONSTEXPR_LENGTH , MODULO ) \ static ll ANSWER[CONSTEXPR_LENGTH]; \ static ll ANSWER_INV[CONSTEXPR_LENGTH]; \ static ll INVERSE[CONSTEXPR_LENGTH]; \ { \ ll VARIABLE_FOR_PRODUCT_FOR_FACTORIAL = 1; \ ANSWER[0] = VARIABLE_FOR_PRODUCT_FOR_FACTORIAL; \ FOREQ( i , 1 , MAX_INDEX ){ \ ANSWER[i] = ( VARIABLE_FOR_PRODUCT_FOR_FACTORIAL *= i ) %= MODULO; \ } \ ANSWER_INV[0] = ANSWER_INV[1] = INVERSE[1] = VARIABLE_FOR_PRODUCT_FOR_FACTORIAL = 1; \ FOREQ( i , 2 , MAX_INDEX ){ \ ANSWER_INV[i] = ( VARIABLE_FOR_PRODUCT_FOR_FACTORIAL *= INVERSE[i] = MODULO - ( ( ( MODULO / i ) * INVERSE[MODULO % i] ) % MODULO ) ) %= MODULO; \ } \ } \ // 通常の二分探索その1 // EXPRESSIONがANSWERの狭義単調増加関数の時、EXPRESSION >= TARGETを満たす最小の整数を返す。 // 広義単調増加関数を扱いたい時は等号成立の処理を消して続く>に等号を付ける。 #define BS1( ANSWER , MINIMUM , MAXIMUM , EXPRESSION , TARGET ) \ static_assert( ! is_same::value && ! is_same::value ); \ ll ANSWER = MINIMUM; \ if( MINIMUM <= MAXIMUM ){ \ ll VARIABLE_FOR_BINARY_SEARCH_L = MINIMUM; \ ll VARIABLE_FOR_BINARY_SEARCH_U = MAXIMUM; \ ANSWER = ( VARIABLE_FOR_BINARY_SEARCH_L + VARIABLE_FOR_BINARY_SEARCH_U ) / 2; \ ll VARIABLE_FOR_DIFFERENCE_FOR_BINARY_SEARCH; \ while( VARIABLE_FOR_BINARY_SEARCH_L != VARIABLE_FOR_BINARY_SEARCH_U ){ \ VARIABLE_FOR_DIFFERENCE_FOR_BINARY_SEARCH = ( EXPRESSION ) - ( TARGET ); \ CERR( VARIABLE_FOR_BINARY_SEARCH_L << "<=" << ANSWER << "<=" << VARIABLE_FOR_BINARY_SEARCH_U << ":" << EXPRESSION << "-" << TARGET << "=" << VARIABLE_FOR_DIFFERENCE_FOR_BINARY_SEARCH ); \ if( VARIABLE_FOR_DIFFERENCE_FOR_BINARY_SEARCH == 0 ){ \ break; \ } else { \ if( VARIABLE_FOR_DIFFERENCE_FOR_BINARY_SEARCH > 0 ){ \ VARIABLE_FOR_BINARY_SEARCH_U = ANSWER; \ } else { \ VARIABLE_FOR_BINARY_SEARCH_L = ANSWER + 1; \ } \ ANSWER = ( VARIABLE_FOR_BINARY_SEARCH_L + VARIABLE_FOR_BINARY_SEARCH_U ) / 2; \ } \ } \ CERR( VARIABLE_FOR_BINARY_SEARCH_L << "<=" << ANSWER << "<=" << VARIABLE_FOR_BINARY_SEARCH_U << ":" << EXPRESSION << "-" << TARGET << ">=0" ); \ } else { \ CERR( MINIMUM << ">" << MAXIMUM << "です。" ); \ } \ // 通常の二分探索その2 // EXPRESSIONがANSWERの狭義単調増加関数の時、EXPRESSION <= TARGETを満たす最大の整数を返す。 // 広義単調増加関数を扱いたい時は等号成立の処理を消して続く<に等号を付ける。 #define BS2( ANSWER , MINIMUM , MAXIMUM , EXPRESSION , TARGET ) \ static_assert( ! is_same::value && ! is_same::value ); \ ll ANSWER = MINIMUM; \ if( MINIMUM <= MAXIMUM ){ \ ll VARIABLE_FOR_BINARY_SEARCH_L = MINIMUM; \ ll VARIABLE_FOR_BINARY_SEARCH_U = MAXIMUM; \ ANSWER = ( VARIABLE_FOR_BINARY_SEARCH_L + VARIABLE_FOR_BINARY_SEARCH_U ) / 2; \ ll VARIABLE_FOR_DIFFERENCE_FOR_BINARY_SEARCH; \ while( VARIABLE_FOR_BINARY_SEARCH_L != VARIABLE_FOR_BINARY_SEARCH_U ){ \ VARIABLE_FOR_DIFFERENCE_FOR_BINARY_SEARCH = ( EXPRESSION ) - ( TARGET ); \ CERR( VARIABLE_FOR_BINARY_SEARCH_L << "<=" << ANSWER << "<=" << VARIABLE_FOR_BINARY_SEARCH_U << ":" << EXPRESSION << "-" << TARGET << "=" << VARIABLE_FOR_DIFFERENCE_FOR_BINARY_SEARCH ); \ if( VARIABLE_FOR_DIFFERENCE_FOR_BINARY_SEARCH == 0 ){ \ break; \ } else { \ if( VARIABLE_FOR_DIFFERENCE_FOR_BINARY_SEARCH < 0 ){ \ VARIABLE_FOR_BINARY_SEARCH_L = ANSWER; \ } else { \ VARIABLE_FOR_BINARY_SEARCH_U = ANSWER - 1; \ } \ ANSWER = ( VARIABLE_FOR_BINARY_SEARCH_L + 1 + VARIABLE_FOR_BINARY_SEARCH_U ) / 2; \ } \ } \ CERR( VARIABLE_FOR_BINARY_SEARCH_L << "<=" << ANSWER << "<=" << VARIABLE_FOR_BINARY_SEARCH_U << ":" << EXPRESSION << "-" << TARGET << "<=0" ); \ } else { \ CERR( MINIMUM << ">" << MAXIMUM << "です。" ); \ } \ // 通常の二分探索その3 // EXPRESSIONがANSWERの狭義単調減少関数の時、EXPRESSION >= TARGETを満たす最大の整数を返す。 // 広義単調増加関数を扱いたい時は等号成立の処理を消して続く>に等号を付ける。 #define BS3( ANSWER , MINIMUM , MAXIMUM , EXPRESSION , TARGET ) \ static_assert( ! is_same::value && ! is_same::value ); \ ll ANSWER = MINIMUM; \ if( MINIMUM <= MAXIMUM ){ \ ll VARIABLE_FOR_BINARY_SEARCH_L = MINIMUM; \ ll VARIABLE_FOR_BINARY_SEARCH_U = MAXIMUM; \ ANSWER = ( VARIABLE_FOR_BINARY_SEARCH_L + VARIABLE_FOR_BINARY_SEARCH_U ) / 2; \ ll VARIABLE_FOR_DIFFERENCE_FOR_BINARY_SEARCH; \ while( VARIABLE_FOR_BINARY_SEARCH_L != VARIABLE_FOR_BINARY_SEARCH_U ){ \ VARIABLE_FOR_DIFFERENCE_FOR_BINARY_SEARCH = ( EXPRESSION ) - ( TARGET ); \ CERR( VARIABLE_FOR_BINARY_SEARCH_L << "<=" << ANSWER << "<=" << VARIABLE_FOR_BINARY_SEARCH_U << ":" << EXPRESSION << "-" << TARGET << "=" << VARIABLE_FOR_DIFFERENCE_FOR_BINARY_SEARCH ); \ if( VARIABLE_FOR_DIFFERENCE_FOR_BINARY_SEARCH == 0 ){ \ break; \ } else { \ if( VARIABLE_FOR_DIFFERENCE_FOR_BINARY_SEARCH > 0 ){ \ VARIABLE_FOR_BINARY_SEARCH_L = ANSWER; \ } else { \ VARIABLE_FOR_BINARY_SEARCH_U = ANSWER - 1; \ } \ ANSWER = ( VARIABLE_FOR_BINARY_SEARCH_L + 1 + VARIABLE_FOR_BINARY_SEARCH_U ) / 2; \ } \ } \ CERR( VARIABLE_FOR_BINARY_SEARCH_L << "<=" << ANSWER << "<=" << VARIABLE_FOR_BINARY_SEARCH_U << ":" << EXPRESSION << "-" << TARGET << ">=0" ); \ } else { \ CERR( MINIMUM << ">" << MAXIMUM << "です。" ); \ } \ // 通常の二分探索その4 // EXPRESSIONがANSWERの狭義単調減少関数の時、EXPRESSION <= TARGETを満たす最小の整数を返す。 // 広義単調増加関数を扱いたい時は等号成立の処理を消して続く<に等号を付ける。 #define BS4( ANSWER , MINIMUM , MAXIMUM , EXPRESSION , TARGET ) \ static_assert( ! is_same::value && ! is_same::value ); \ ll ANSWER = MINIMUM; \ if( MINIMUM <= MAXIMUM ){ \ ll VARIABLE_FOR_BINARY_SEARCH_L = MINIMUM; \ ll VARIABLE_FOR_BINARY_SEARCH_U = MAXIMUM; \ ANSWER = ( VARIABLE_FOR_BINARY_SEARCH_L + VARIABLE_FOR_BINARY_SEARCH_U ) / 2; \ ll VARIABLE_FOR_DIFFERENCE_FOR_BINARY_SEARCH; \ while( VARIABLE_FOR_BINARY_SEARCH_L != VARIABLE_FOR_BINARY_SEARCH_U ){ \ VARIABLE_FOR_DIFFERENCE_FOR_BINARY_SEARCH = ( EXPRESSION ) - ( TARGET ); \ CERR( VARIABLE_FOR_BINARY_SEARCH_L << "<=" << ANSWER << "<=" << VARIABLE_FOR_BINARY_SEARCH_U << ":" << EXPRESSION << "-" << TARGET << "=" << VARIABLE_FOR_DIFFERENCE_FOR_BINARY_SEARCH ); \ if( VARIABLE_FOR_DIFFERENCE_FOR_BINARY_SEARCH == 0 ){ \ break; \ } else { \ if( VARIABLE_FOR_DIFFERENCE_FOR_BINARY_SEARCH < 0 ){ \ VARIABLE_FOR_BINARY_SEARCH_U = ANSWER; \ } else { \ VARIABLE_FOR_BINARY_SEARCH_L = ANSWER + 1; \ } \ ANSWER = ( VARIABLE_FOR_BINARY_SEARCH_L + VARIABLE_FOR_BINARY_SEARCH_U ) / 2; \ } \ } \ CERR( VARIABLE_FOR_BINARY_SEARCH_L << "<=" << ANSWER << "<=" << VARIABLE_FOR_BINARY_SEARCH_U << ":" << EXPRESSION << "-" << TARGET << "<=0" ); \ } else { \ CERR( MINIMUM << ">" << MAXIMUM << "です。" ); \ } \ // 二進法の二分探索 // EXPRESSIONがANSWERの狭義単調増加関数の時、EXPRESSION <= TARGETを満たす最大の整数を返す。 #define BBS( ANSWER , MINIMUM , MAXIMUM , EXPRESSION , TARGET ) \ ll ANSWER = MINIMUM; \ if( MINIMUM <= MAXIMUM ){ \ ll VARIABLE_FOR_POWER_FOR_BINARY_SEARCH = 1; \ ll VARIABLE_FOR_DIFFERENCE_FOR_BINARY_SEARCH = ( MAXIMUM ) - ANSWER; \ while( VARIABLE_FOR_POWER_FOR_BINARY_SEARCH <= VARIABLE_FOR_DIFFERENCE_FOR_BINARY_SEARCH ){ \ VARIABLE_FOR_POWER_FOR_BINARY_SEARCH *= 2; \ } \ VARIABLE_FOR_POWER_FOR_BINARY_SEARCH /= 2; \ ll VARIABLE_FOR_ANSWER_FOR_BINARY_SEARCH = ANSWER; \ while( VARIABLE_FOR_POWER_FOR_BINARY_SEARCH != 0 ){ \ ANSWER = VARIABLE_FOR_ANSWER_FOR_BINARY_SEARCH + VARIABLE_FOR_POWER_FOR_BINARY_SEARCH; \ VARIABLE_FOR_DIFFERENCE_FOR_BINARY_SEARCH = ( EXPRESSION ) - ( TARGET ); \ if( VARIABLE_FOR_DIFFERENCE_FOR_BINARY_SEARCH == 0 ){ \ VARIABLE_FOR_ANSWER_FOR_BINARY_SEARCH = ANSWER; \ break; \ } else if( VARIABLE_FOR_DIFFERENCE_FOR_BINARY_SEARCH < 0 ){ \ VARIABLE_FOR_ANSWER_FOR_BINARY_SEARCH = ANSWER; \ } \ VARIABLE_FOR_POWER_FOR_BINARY_SEARCH /= 2; \ } \ ANSWER = VARIABLE_FOR_ANSWER_FOR_BINARY_SEARCH; \ } else { \ CERR( MINIMUM << ">" << MAXIMUM << "です。" ); \ } \ // 圧縮用 #define TE template #define TY typename #define US using #define ST static #define IN inline #define CL class #define PU public #define OP operator #define CE constexpr #define CO const #define NE noexcept #define RE return #define WH while #define VO void #define VE vector #define LI list #define BE begin #define EN end #define SZ size #define MO move #define TH this #define CRI CO int& #define CRUI CO uint& #define CRL CO ll& int QuitLibrarySearch( const int& problems_size ){ cerr << "返答は" << problems_size - 1 << "以下の非負整数にしてください。"; CERR( "終了します。" ); CERR( "" ); return -1; } int LibrarySearch( int num = -1 ) { vector problems = { "数に関する問題。" , "配列に関する問題。" , "文字列に関する問題。" , "順列に関する問題。" , "矩形領域に関する問題。" , "グラフに関する問題。" , "部分和問題。" , "期待値に関する問題。" , "ゲームに関する問題。" , "論理に関する問題。" , "順序に関する問題。" , "関数適用に関する問題。" , "構築問題。" , "関数適用に関する問題。" , "構築問題。" }; CEXPR( int , num_graph , 5 ); CEXPR( int , num_subsequence_sum , 6 ); CEXPR( int , num_game , 8 ); int problems_size = problems.size(); string reply{}; if( num == -1 ){ CERR( "ライブラリーを探索しますか?[y/n]" ); CIN( string , reply ); if( reply == "n" ){ CERR( "ライブラリーを探索せずに続行します。" ); CERR( "" ); return 0; } else if( reply != "y" ){ CERR( "y/nのいずれかで答えてください。" ); CERR( "終了します。" ); CERR( "" ); return -1; } CERR( "" ); CERR( "ライブラリーを探索します。" ); CERR( "問題の種類を番号で指定してください;" ); FOR( i , 0 , problems_size ){ CERR( i << ": " << problems[i] ); } cin >> num; } CERR( "" ); int num_temp = 0; if( num < 0 || num >= problems_size ){ return QuitLibrarySearch( problems_size ); } else if( num == num_temp++ ){ CERR( "入力は1つの数か、1つの数と法を表す数ですか?[y/n/c]" ); cin >> reply; CERR( "" ); if( reply == "y" ){ CERR( "まずは小さい入力の場合を愚直に計算し、OEISで検索しましょう。" ); CERR( "https://oeis.org/?language=japanese" ); CERR( "" ); CERR( "次に出力の定義と等価な式を考察しましょう。" ); CERR( "- 単調ならば、冪乗や階乗" ); CERR( "- 定義にp進法が使われていれば、各種探索アルゴリズム" ); CERR( "- 入力が素数に近い場合に規則性があれば、p進付値、p進法、" ); CERR( " オイラー関数、約数の個数など" ); CERR( "を検討しましょう。" ); } else if( reply == "n" ){ CERR( "このケースのライブラリー探索は未実装です。" ); } else if( reply == "c" ){ CERR( "終了します。" ); CERR( "" ); return -1; } else { CERR( "y/n/cのいずれかで答えてください。" ); CERR( "終了します。" ); CERR( "" ); return -1; } CERR( "" ); CERR( "マルチテストケースの場合は以下の前計算を検討しましょう;" ); CERR( "素数列挙、約数列挙、サブゴールとなる関係式を満たす解列挙。" ); } else if( num == num_temp++ ){ CERR( "より詳細な問題の種類を番号で指定してください;" ); problems = { "区間処理問題。" , "最大化問題。" , "最長部分列問題。" , "数え上げ問題。" , "部分和問題。" , }; problems_size = problems.size(); FOR( i , 0 , problems_size ){ CERR( i << ": " << problems[i] ); } CIN( int , num ); CERR( "" ); num_temp = 0; if( num < 0 || num >= problems_size ){ return QuitLibrarySearch( problems_size ); } else if( num == num_temp++ ){ CERR( "代数の問題なので頑張ってください。" ); CERR( "Mathematics\\SetTheory\\DirectProduct\\AffineSpace" ); } else if( num == num_temp++ ){ CERR( "最大化すべき式のサブゴールfに表れる項xのうち決め打ちやすいものを探しましょう。" ); CERR( "- 配列の長さをiで打ち切った時のxの候補数をX(i)" ); CERR( "- 配列の長さをiで打ち切ってxを決め打った時の配列の長さi+1でのxの候補数をdX(i)" ); CERR( "と置きます。" ); CERR( "- O(sum_i X(i) dX(i))が通りそうでfがxからO(1)で計算できるならば、iとxに関する動的計画法" ); CERR( "- O(N log_2 X)が通りそうでfがxからO(N)で計算できxに関して単調ならば、xの二分探索" ); CERR( "- O(N log_2 N)が通りそうでxを上手く並び替えるとfがxからO(log_2 N)で計算できるならば、" ); CERR( " 優先度つきキューなどでのxの管理" ); CERR( "を検討しましょう。" ); } else if( num == num_temp++ ){ CERR( "全順序か疎な半順序かで効率的な実装が違います。" ); CERR( "- 全順序ならば、条件を満たす部分列の長さの最大値をインデックスに持つ配列を用いて、" ); CERR( " それらの部分列の末尾である項を記録すること" ); CERR( "- 疎な半順序ならば、条件を満たす部分列の末尾をインデックスに持つ連想配列を用いて、" ); CERR( " それら部分列の長さの最大値を記録すること" ); CERR( "を検討しましょう。" ); } else if( num == num_temp++ ){ CERR( "gcdやmaxなどの羃等演算に関する等式を指定した数え上げは不等式の方が" ); CERR( "扱いやすいのでゼータ変換/メビウス変換を検討しましょう。" ); } else { return LibrarySearch( num = num_subsequence_sum ); } } else if( num == num_temp++ ){ CERR( "より詳細な問題の種類を番号で指定しましょう;" ); problems = { "部分列マッチング問題。" , "回文探索問題。" , }; problems_size = problems.size(); FOR( i , 0 , problems_size ){ CERR( i << ": " << problems[i] ); } CIN( int , num ); CERR( "" ); num_temp = 0; if( num < 0 || num >= problems_size ){ return QuitLibrarySearch( problems_size ); } else if( num == num_temp++ ){ CERR( "基本的には丁寧にループを回して解きましょう。" ); CERR( "- 比較対象が少ない場合、前または後ろから順に探索(貪欲法/動的計画法)" ); CERR( "- ワイルドカードを含む場合、" ); CERR( " - 前または後ろから順に場合分けをしてO(N)で処理できるか" ); CERR( " - 可能な代入方法を絞り込んでO(N)種類に落せるか" ); CERR( "- 比較回数が多い場合、ローリングハッシュ" ); CERR( "- マッチングする文字列の最長化をする場合、Zアルゴリズム" ); CERR( " https://qiita.com/Pro_ktmr/items/16904c9570aa0953bf05" ); CERR( "を検討しましょう。" ); } else { CERR( "回文判定は長さに関して再帰的に計算できます。" ); CERR( "- O(N^2)が通る場合、愚直な再帰により前計算で全ての部分列の回文判定" ); CERR( "- O(N^2)が通らない場合、Manacherのアルゴリズムやローリングハッシュで前計算" ); CERR( " https://snuke.hatenablog.com/entry/2014/12/02/235837" ); CERR( "を検討しましょう。" ); } } else if( num == num_temp++ ){ CERR( "符号の計算は転倒数の計算に帰着させましょう。" ); CERR( "符号と何かの積の和は行列式に帰着させましょう。" ); CERR( "余因子展開のメモ化再帰でO(N 2^N)です。" ); CERR( "" ); CERR( "1つの順列の転倒数は、" ); CERR( "- O(N^2)が通りそうならば愚直な二重ループ" ); CERR( "- O(N log_2 N)が通りそうならば可換群に対するBIT" ); CERR( " \\Mathematics\\Combinatorial\\Permutation" ); CERR( " \\Mathematics\\SetTheory\\DirectProduct\\AffineSpace\\BIT" ); CERR( "で計算しましょう。" ); CERR( "" ); CERR( "条件を満たす順列全体をわたる転倒数の総和は、" ); CERR( "各i= problems_size ){ return QuitLibrarySearch( problems_size ); } else if( num == num_temp++ ){ CERR( "隣接関係の定める無向グラフの問題に帰着させましょう。" ); return LibrarySearch( num = num_graph ); } else if( num == num_temp++ ){ CERR( "符号を用いて絶対値を外しましょう。" ); CERR( "- 単調な式に帰着できる場合、二分探索を検討しましょう。" ); CERR( "- 最大化問題の場合、符号パターンの全探策を検討しましょう。" ); CERR( "- マンハッタン距離などは一次変換で簡単にすることを検討しましょう。" ); CERR( "複数のパラメータを決定すべき場合は、サブゴールの式の値を決め打ちましょう。" ); } else if( num == num_temp++ ){ CERR( "このケースのライブラリー探索は不完全です。" ); CERR( "- O(HW)が通りそうならば動的計画法" ); CERR( "- O(HW)が通らなさそうならば見方を変えて最短経路問題に帰着できないか" ); CERR( "を検討しましょう。" ); CERR( "" ); CERR( "例えば迷路の攻略可能性は" ); CERR( "- スタートとゴールが同一の弧状連結成分に属すこと" ); CERR( "- スタートとゴールを分断する壁のパスの非存在性" ); CERR( "などから翻訳できます。" ); } } else if( num == num_temp++ ){ CERR( "より詳細な問題の種類を番号で指定してください;" ); problems = { "2点最短径路(迷路)問題。" , "多点最短経路(スタンプラリー)問題。" , "木の問題。" , "連結性問題。" }; problems_size = problems.size(); FOR( i , 0 , problems_size ){ CERR( i << ": " << problems[i] ); } CIN( int , num ); CERR( "" ); num_temp = 0; if( num < 0 || num >= problems_size ){ return QuitLibrarySearch( problems_size ); } else if( num == num_temp++ ){ CERR( "特定の経路を進むと思い込んで考察漏れをする可能性があります。" ); CERR( "なるべく全ての経路を許した探索アルゴリズムを適用した方が無難です。" ); CERR( "- 特定の2点のみを考える場合、BFSやDijkstra" ); CERR( " \\Utility\\Search\\BreadthFirst" ); CERR( " \\Utility\\Search\\Dijkstra" ); CERR( "- 全ての2点の組み合わせを考える場合、" ); CERR( " - 一般のモノイド演算を考えておりO(V^3)が通りそうならば、FloydWarshall" ); CERR( " \\Utility\\Search\\FloydWarshall" ); CERR( " - max演算を考えておりO(E(log_2 E + α(V)))が通りそうならば、UnionFind" ); CERR( " \\Utility\\VLTree\\UnionFindForest" ); CERR( "を検討しましょう。" ); } else if( num == num_temp++ ){ CERR( "HeldKarpや、移動方法を分類するパラメータの全探策などを検討しましょう。" ); } else if( num == num_temp++ ){ CERR( "深さ優先探索や動的木を検討しましょう。" ); CERR( "\\Utility\\Search\\DepthFirst" ); CERR( "\\Utility\\VLTree" ); } else { CERR( "- 0次の連結性はUnionFind" ); CERR( " \\Utility\\VLTree\\UnionFindForest" ); CERR( "- 高次の連結性は深さ優先探索" ); CERR( " \\Utility\\Search\\DepthFirst" ); CERR( "- マルチテストケースの場合は座標圧縮との併用" ); CERR( "を検討しましょう。" ); } } else if( num == num_temp++ ){ CERR( "より詳細な問題の種類を番号で指定してください;" ); problems = { "部分和の最大化問題。" , "部分和の数え上げ問題。" }; problems_size = problems.size(); FOR( i , 0 , problems_size ){ CERR( i << ": " << problems[i] ); } CIN( int , num ); CERR( "" ); num_temp = 0; if( num < 0 || num >= problems_size ){ return QuitLibrarySearch( problems_size ); } else if( num == num_temp++ ){ CERR( "項数N、重みの総和の上限W(多変数も可)、価値(和を取る値)の上限Vとします。" ); CERR( "- B=∞ならば、通常のナップサック問題と同様の動的計画法" ); CERR( "- B<∞でO(2^N)が通りそうならば愚直に全探策" ); CERR( "- B<∞でO(2^{N/2} N)が通りそうならば半分全列挙" ); CERR( "- B<∞で重みと価値が等しくO(NV)が通りそうならば[B-V,B+V]での実現可能性を" ); CERR( " 遷移する動的計画法" ); CERR( " https://stackoverflow.com/a/18949218" ); CERR( "- Wが10^5オーダーで重みと価値が等しくO((N+W)log_2 W)が通りそうならば" ); CERR( " 適当な法での畳み込み(確率的解法)" ); CERR( " \\Mathematics\\Polynomial" ); } else { CERR( "項数N、重みの総和の上限Wとします。" ); CERR( "- 重みと価値が異なりO(2^N)が通りそうならば愚直な2変数多項式乗算" ); CERR( "- 重みと価値が等しくO(2^N)が通りそうならば愚直な1変数多項式乗算" ); CERR( "- 重みと価値が等しくO(2^{N/2}N)が通りそうならば半分ずつ多項式乗算を" ); CERR( " して最後にそれらの積の1係数のみの計算" ); CERR( "- 重みと価値が等しくWが10^5オーダーで法が与えられていてO((N+W)log_2 W)が" ); CERR( " 通りそうならば畳み込み" ); CERR( " \\Mathematics\\Polynomial" ); } CERR( "を検討しましょう。" ); } else if( num == num_temp++ ){ CERR( "線形性を使いましょう。" ); } else if( num == num_temp++ ){ CERR( "ゲームの和に分解できる場合は最小単位で考察をし、グランディ数を実装しましょう。" ); CERR( "これ以上分解できないゲームには整礎構造を探し、順序数の小さい順に実験をしましょう。" ); } else if( num == num_temp++ ){ CERR( "複数の命題に対する" ); CERR( "- 区間処理は各種代数的データ構造" ); CERR( " \\Mathematics\\SetTheory\\DirectProduct\\AffineSpace" ); CERR( "- 充足可能性判定は頂点数を2倍してUnionFind" ); CERR( " \\Utility\\VLTree\\UnionFindForest" ); CERR( "を検討しましょう。" ); } else if( num == num_temp++ ){ CERR( "より詳細な問題の種類を番号で指定してください;" ); problems = { "半順序集合上の関数の計算問題。" , "半順序集合上の降下/上昇列に関する問題。" }; problems_size = problems.size(); FOR( i , 0 , problems_size ){ CERR( i << ": " << problems[i] ); } CIN( int , num ); CERR( "" ); num_temp = 0; if( num < 0 || num >= problems_size ){ return QuitLibrarySearch( problems_size ); } else if( num == num_temp++ ){ CERR( "ゼータ変換を検討しましょう。" ); } else if( num == num_temp++ ){ CERR( "順序関係の定める有向グラフの問題に帰着させましょう。" ); return LibrarySearch( num = num_graph ); } } else if( num == num_temp++ ){ CERR( "より詳細な問題の種類を番号で指定してください;" ); problems = { "反復合成の計算問題。" , "反復合成による到達可能性問題。" }; problems_size = problems.size(); FOR( i , 0 , problems_size ){ CERR( i << ": " << problems[i] ); } CIN( int , num ); CERR( "" ); num_temp = 0; if( num < 0 || num >= problems_size ){ return QuitLibrarySearch( problems_size ); } else if( num == num_temp++ ){ CERR( "定義域の要素数N、テストケース数T、反復回数の上限Kとします。" ); CERR( "- O((N + T)log_2 K)が通りそうならばダブリング" ); CERR( " \\Mathematics\\Function\\Iteration\\Doubling" ); CERR( "- O(TN)が通りそうならばループ検出" ); CERR( " \\Mathematics\\Function\\Iteration\\LoopDetection" ); CERR( "を検討しましょう。" ); } else { CERR( "関数による遷移が定める有向グラフの問題に帰着させましょう。" ); return LibrarySearch( num = num_graph ); } } else if( num == num_temp++ ){ CERR( "より詳細な問題の種類を番号で指定してください;" ); problems = { "数や配列や文字列の構築。" , "経路の構築。" , "戦略の構築。" }; problems_size = problems.size(); FOR( i , 0 , problems_size ){ CERR( i << ": " << problems[i] ); } CIN( int , num ); CERR( "" ); num_temp = 0; if( num < 0 || num >= problems_size ){ return QuitLibrarySearch( problems_size ); } else if( num == num_temp++ ){ CERR( "p進法を検討しましょう。" ); } else if( num == num_temp++ ){ CERR( "可能な経路の定める無向グラフの問題に帰着させましょう。" ); return LibrarySearch( num = num_graph ); } else if( num == num_temp++ ){ CERR( "ゲームの問題に帰着させましょう。" ); return LibrarySearch( num = num_game ); } } CERR( "" ); CERR( "ライブラリー探索は以上です。終了します。" ); CERR( "" ); return -1; } // m_size_domのm_size_cod乗が2^64未満の場合のみサポート。 // 特にm_size_dom = m_size_codならば、m_size_dom<=15の場合のみサポート。 template class FunctionEncoderBody { public: uint m_size_dom; uint m_size_cod; private: uint m_size_min; ull m_size_cod_power[size_dom_max]; uint m_length; map m_memory; vector m_memory_inv; public: inline FunctionEncoderBody( const uint& size_dom , const uint& size_cod ); ull Encode( const U& u ); ull Encode( const uint ( &encoded_function )[size_dom_max] ) const; ull Encode( uint ( *encoded_function )(const uint&) ) const; inline T DirectApply( const U& u , const T& t ) const; inline T DecodeApply( const ull& u , const T& t ); ull Composite( const ull& u0 , const ull& u1 ); private: virtual T e( const uint& i ); virtual uint e_inv( const T& t ); }; // fによるエンコードO(size) // コードによる関数適用O(1) // コードの合成O(size) template class FunctionEncoder : public FunctionEncoderBody { public: inline FunctionEncoder( const uint& size ); inline FunctionEncoder( const uint& size_dom , const uint& size_cod ); private: inline uint e( const uint& i ); inline uint e_inv( const uint& t ); }; // fによるエンコードO(size log size) // コードによる関数適用O(log size) // コードの合成O(size) template class MemorisationFunctionEncoder : public FunctionEncoderBody { public: inline MemorisationFunctionEncoder( const uint& size ); }; // fによるエンコードO(size) // コードによる関数適用O(1) // コードの合成O(size) template class EnumerationFunctionEncoder : public FunctionEncoderBody { public: inline EnumerationFunctionEncoder( const uint& size ); private: inline T e( const uint& i ); inline uint e_inv( const T& t ); }; template inline FunctionEncoderBody::FunctionEncoderBody( const uint& size_dom , const uint& size_cod ) : m_size_dom( size_dom ) , m_size_cod( size_cod ) , m_size_min( min( m_size_dom , m_size_cod ) ) , m_size_cod_power() , m_length() , m_memory() , m_memory_inv() { assert( m_size_cod == 0 ? true : m_size_dom <= ull( -1 ) / m_size_cod ); assert( m_size_dom <= size_dom_max ); ull power = 1; for( uint i = 0 ; i < m_size_dom ; i++ , power *= m_size_cod ){ m_size_cod_power[i] = power; } } template inline FunctionEncoder::FunctionEncoder( const uint& size ) : FunctionEncoder( size , size ) {} template inline FunctionEncoder::FunctionEncoder( const uint& size_dom , const uint& size_cod ) : FunctionEncoderBody( size_dom , size_cod ) {} template inline MemorisationFunctionEncoder::MemorisationFunctionEncoder( const uint& size ) : FunctionEncoderBody( size , size ) {} template inline EnumerationFunctionEncoder::EnumerationFunctionEncoder( const uint& size ) : FunctionEncoderBody( size , size ) {} template ull FunctionEncoderBody::Encode( const U& u ) { ull answer = 0; for( uint i = 0 ; i < m_size_dom ; i++ ){ const uint& f_i = e_inv( f( u , e( i ) ) ); answer += f_i * m_size_cod_power[i]; } return answer; } template ull FunctionEncoderBody::Encode( const uint ( &encoded_function )[size_dom_max] ) const { ull answer = 0; for( uint i = 0 ; i < m_size_dom ; i++ ){ const uint& encoded_function_i = encoded_function[i]; assert( encoded_function_i < m_size_cod ); answer += encoded_function_i * m_size_cod_power[i]; } return answer; } template ull FunctionEncoderBody::Encode( uint ( *encoded_function )(const uint&) ) const { ull answer = 0; for( uint i = 0 ; i < m_size_dom ; i++ ){ const uint& encoded_function_i = encoded_function( i ); assert( encoded_function_i < m_size_cod ); answer += encoded_function_i * m_size_cod_power[i]; } return answer; } template inline T FunctionEncoderBody::DirectApply( const U& u , const T& t ) const { return f( u , t ); } template inline T FunctionEncoderBody::DecodeApply( const ull& u , const T& t ) { return e( ( u / m_size_cod_power[ e_inv( t ) ] ) % m_size_cod ); } template ull FunctionEncoderBody::Composite( const ull& u0 , const ull& u1 ) { ull answer = 0; for( uint i = 0 ; i < m_size_dom ; i++ ){ const uint& u1i = DecodeApply( u1 , e( i ) ); assert( u1i < m_size_min ); const uint& u0u1i = DecodeApply( u0 , e( u1i ) ); answer += u0u1i * m_size_cod_power[i]; } return answer; } template T FunctionEncoderBody::e( const uint& i ) { assert( i < min( m_length , m_size_dom ) ); return m_memory_inv[i]; } template inline uint FunctionEncoder::e( const uint& i ) { using base = FunctionEncoderBody; assert( i < base::m_size_dom ); return i; } template inline T EnumerationFunctionEncoder::e( const uint& i ) { using base = FunctionEncoderBody; assert( i < base::m_size_dom ); return enum_T( i ); } template uint FunctionEncoderBody::e_inv( const T& t ) { if( m_memory.count( t ) == 0 ){ assert( m_length < m_size_cod ); m_memory_inv.push_back( t ); return m_memory[t] = m_length++; } return m_memory[t]; } template inline uint FunctionEncoder::e_inv( const uint& t ) { using base = FunctionEncoderBody; assert( t < base::m_size_cod ); return t; } template inline uint EnumerationFunctionEncoder::e_inv( const T& t ) { using base = FunctionEncoderBody; const uint i = enum_T_inv( t ); assert( i < base::m_size_cod ); return i; } inline CEXPR( uint , ulim_ABC , 4 ); uint prod[ulim_ABC][ulim_ABC] = {}; inline uint f( const uint& u , const uint& t ) { return prod[u][t]; } int main() { UNTIE; LIBRARY_SEARCH; // CEXPR( int , bound_T , 100000 ); // CIN_ASSERT( T , 1 , bound_T ); CEXPR( int , bound_N , 100000 ); // 0が5個 CIN_ASSERT( N , 3 , bound_N ); // CEXPR( ll , bound_N , 1000000000 ); // 0が9個 // CEXPR( ll , bound_N , 1000000000000000000 ); // 0が18個 // CIN_ASSERT( N , 0 , bound_N ); // CEXPR( int , bound_M , 100000 ); // 0が5個 // CEXPR( ll , bound_M , 1000000000 ); // 0が9個 // CEXPR( ll , bound_M , 1000000000000000000 ); // 0が18個 // CEXPR( ll , bound_M , 1000000000000000000 ); // 0が18個 CIN_ASSERT( A , 0 , ulim_ABC - 1 ); CIN_ASSERT( B , 0 , ulim_ABC - 1 ); CIN_ASSERT( C , 0 , ulim_ABC - 1 ); FOR( u , 0 , ulim_ABC ){ uint ( &prod_u )[ulim_ABC] = prod[u]; FOR( t , 0 , ulim_ABC ){ prod_u[t] = ( A * ( u & t ) + B * ( u | t ) + C * ( u ^ t ) ) % ulim_ABC; } } FunctionEncoder fe{ ulim_ABC }; ull memory_code[ulim_ABC]; FOR( n , 0 , ulim_ABC ){ memory_code[n] = fe.Encode( n ); } CEXPR( uint , ulim_code , ulim_ABC * ulim_ABC * ulim_ABC * ulim_ABC ); uint dp[2][ulim_code][ulim_ABC] = {}; uint i_curr = 0; uint i_prev = 1; { uint ( &dp_curr )[ulim_code][ulim_ABC] = dp[i_curr]; FOR( n , 0 , ulim_ABC ){ dp_curr[memory_code[n]][n]++; } swap( i_curr , i_prev ); } CEXPR( uint , P , 998244353 ); FOR( i , 2 , N ){ uint ( &dp_curr )[ulim_code][ulim_ABC] = dp[i_curr]; uint ( &dp_prev )[ulim_code][ulim_ABC] = dp[i_prev]; FOR( code , 0 , ulim_code ){ uint ( &dp_prev_code )[ulim_ABC] = dp_prev[code]; FOR( n , 0 , ulim_ABC ){ uint& dp_prev_code_n = dp_prev_code[n]; if( dp_prev_code_n != 0 ){ FOR( m , 0 , ulim_ABC ){ uint& dp_curr_code_n_m = dp_curr[fe.Composite( code , memory_code[m] )][fe.DirectApply( n , m )]; ( dp_curr_code_n_m += dp_prev_code_n ) < P ? dp_curr_code_n_m : dp_curr_code_n_m -= P; } dp_prev_code_n = 0; } } } swap( i_curr , i_prev ); } // REPEAT( T ){ // COUT( N ); // } uint answer = 0; uint ( &dp_last )[ulim_code][ulim_ABC] = dp[i_prev]; FOR( code , 0 , ulim_code ){ uint ( &dp_last_code )[ulim_ABC] = dp_last[code]; FOR( n , 0 , ulim_ABC ){ uint& dp_last_code_n = dp_last_code[n]; if( dp_last_code_n != 0 ){ FOR( m , 0 , ulim_ABC ){ if( fe.DecodeApply( code , m ) == fe.DirectApply( n , m ) ){ ( answer += dp_last_code_n ) < P ? answer : answer -= P; } } } } } RETURN( answer ); }