結果
問題 | No.2744 Power! or +1 |
ユーザー |
|
提出日時 | 2025-02-11 18:10:02 |
言語 | C++14 (gcc 13.3.0 + boost 1.87.0) |
結果 |
AC
|
実行時間 | 1,074 ms / 3,000 ms |
コード長 | 9,556 bytes |
コンパイル時間 | 2,077 ms |
コンパイル使用メモリ | 172,348 KB |
実行使用メモリ | 16,916 KB |
最終ジャッジ日時 | 2025-02-11 18:10:08 |
合計ジャッジ時間 | 5,678 ms |
ジャッジサーバーID (参考情報) |
judge1 / judge5 |
(要ログイン)
ファイルパターン | 結果 |
---|---|
sample | AC * 2 |
other | AC * 9 |
ソースコード
#include <bits/stdc++.h>using namespace std;typedef long long ll;// 十分大きな値(ここでは 1e18)const ll INF = 1000000000000000000LL;// fast modular exponentiation: (base^exp) mod mod を返すint modExp(int base, int exp, int mod) {ll result = 1;ll b = base % mod;while(exp > 0){if(exp & 1) result = (result * b) % mod;b = (b * b) % mod;exp >>= 1;}return (int)result;}// T0 を計算する関数// T0 は「t! が N で割り切れる(すなわち,各素 p について// sum_{i>=1} floor(t/p^i) >= v_p(N) となる t)の中で最小の t」int computeT0(int N) {int temp = N;vector<pair<int,int>> factors;// 試し割りで N の素因数分解(N ≤ 2e5 なので十分高速)for (int p = 2; p*p <= temp; p++){if(temp % p == 0){int cnt = 0;while(temp % p == 0){cnt++;temp /= p;}factors.push_back({p, cnt});}}if(temp > 1) factors.push_back({temp, 1});int T0 = 1;// 各素因数 p^a について,最小の t を求める.for(auto &pr : factors){int p = pr.first, cnt = pr.second;int lo = 1, hi = N; // t は N 以下で十分int best = hi;while(lo <= hi){int mid = (lo + hi) / 2;ll sum = 0;ll power = p;while(power <= mid){sum += mid / power;if(power > mid / p) break;power *= p;}if(sum >= cnt){best = mid;hi = mid - 1;} else {lo = mid + 1;}}T0 = max(T0, best);}return T0;}// 状態の定義// phase == 0 : 実値 X(X < T0)を状態として持つ(フェーズ1)// phase == 1 : X の剰余 r = X mod N を状態として持つ(フェーズ2;X ≥ T0 と仮定)struct State {ll cost;int phase; // 0: phase1, 1: phase2int val; // phase1 の場合は X, phase2 の場合は r (0 <= r < N)bool operator>(const State &other) const {return cost > other.cost;}};// メインint main(){ios::sync_with_stdio(false);cin.tie(nullptr);int N, A, B, C;cin >> N >> A >> B >> C;// 制約より N, A, B, C は 2 以上// T0 を計算:フェーズ1では X < T0 の状態を扱うint T0 = computeT0(N);// (T0 は必ず N 以下になります)// 【前処理】フェーズ1で使うために,1 ≤ x < T0 に対する x! mod N を計算vector<int> fact_mod(T0, 0);// 1-indexed で扱う(index 0 は使わない)fact_mod[1] = 1 % N;for (int i = 2; i < T0; i++){fact_mod[i] = (int)(((ll)fact_mod[i-1] * i) % N);}// また,x! の値が T0 未満かどうかを判定するため,// 実際の x! (ただし T0 未満なら正確な値,T0 以上なら T0 とする) を precomputevector<ll> fact_exact(T0, 0);fact_exact[1] = 1;for (int i = 2; i < T0; i++){// もし fact_exact[i-1] * i < T0 なら正確な値,// そうでなければ T0 以上とみなして T0 をセットif(fact_exact[i-1] > (T0 - 1LL) / i)fact_exact[i] = T0;elsefact_exact[i] = fact_exact[i-1] * i;}// dp1[x]: フェーズ1 (実値状態) で X = x のときの最小コスト (1 ≤ x < T0)// dp2[r]: フェーズ2 (剰余状態) で X mod N = r のときの最小コストvector<ll> dp1(T0, INF);vector<ll> dp2(N, INF);// 優先度付きキュー(Dijkstra用)priority_queue<State, vector<State>, greater<State>> pq;// 初期状態:X = 1 (フェーズ1)dp1[1] = 0;pq.push({0, 0, 1});while(!pq.empty()){State cur = pq.top();pq.pop();ll curCost = cur.cost;int phase = cur.phase;int curVal = cur.val;if(phase == 0){ // フェーズ1:状態 X (1 ≤ X < T0)if(curCost != dp1[curVal]) continue; // 更新済みなら飛ばす// 【操作1】+1: X -> X+1 (コスト A)int newX = curVal + 1;ll newCost = curCost + A;if(newX < T0){if(newCost < dp1[newX]){dp1[newX] = newCost;pq.push({newCost, 0, newX});}} else {int r = newX % N;if(newCost < dp2[r]){dp2[r] = newCost;pq.push({newCost, 1, r});}}// 【操作2】累乗: X -> X^k, k ≥ 2, コストは B^k// ※ X=1 のときは 1^k = 1 なので効果がないのでスキップif(curVal != 1){ll power = curVal; // 現在は X^1ll costPow = B; // このループで k=2 から始めるため、最初 costPow に B を掛けて B^2 とするfor (int k = 2; ; k++){costPow *= B; // 現在 costPow = B^kif(curCost > INF - costPow) break; // オーバーフロー防止ll nextCost = curCost + costPow;// X^k を計算する際,もし power * X < T0 ならフェーズ1内で遷移可能if(power <= (T0 - 1LL) / curVal){ll newVal = power * curVal;if(newVal < T0){if(nextCost < dp1[newVal]){dp1[newVal] = nextCost;pq.push({nextCost, 0, (int)newVal});}power = newVal; // 次の k で使うif(costPow > INF / B) break;continue;} else {// newVal >= T0 ならフェーズ2へ遷移(実際の値は捨て,剰余 mod N を記録)int r = modExp(curVal, k, N);if(nextCost < dp2[r]){dp2[r] = nextCost;pq.push({nextCost, 1, r});}break;}} else {// これ以上フェーズ1内で計算できないので,フェーズ2へint r = modExp(curVal, k, N);if(nextCost < dp2[r]){dp2[r] = nextCost;pq.push({nextCost, 1, r});}break;}if(costPow > INF / B) break;}}// 【操作3】階乗: X -> X! (コスト C)// 事前計算した fact_exact, fact_mod を利用する.ll fval = fact_exact[curVal];ll newCostF = curCost + C;if(fval < T0){int newX = (int) fval;if(newCostF < dp1[newX]){dp1[newX] = newCostF;pq.push({newCostF, 0, newX});}} else {int r = fact_mod[curVal];if(newCostF < dp2[r]){dp2[r] = newCostF;pq.push({newCostF, 1, r});}}} else { // フェーズ2:状態は剰余 r (0 ≤ r < N),ここでは X ≥ T0 と仮定if(curCost != dp2[curVal]) continue;// 既に r == 0 なら X は N の倍数if(curVal == 0){cout << curCost << "\n";return 0;}// 【操作1】+1: 剰余 r -> (r+1) mod N (コスト A)int new_r = (curVal + 1) % N;ll newCost = curCost + A;if(newCost < dp2[new_r]){dp2[new_r] = newCost;pq.push({newCost, 1, new_r});}// 【操作2】累乗: X -> X^k と同様に,剰余も r -> r^k mod N (コスト B^k)// ※ r=0,1 のときは変化しないので対象外if(curVal != 0 && curVal != 1){ll expCost = B; // k=2 から開始するためfor (int k = 2; ; k++){expCost *= B; // expCost = B^kif(curCost > INF - expCost) break;ll nextCost = curCost + expCost;int r_new = modExp(curVal, k, N);if(nextCost < dp2[r_new]){dp2[r_new] = nextCost;pq.push({nextCost, 1, r_new});}// 安全のため、k の上限を適当に設定if(k >= 60) break;if(expCost > INF / B) break;}}// 【操作3】階乗: X! (コスト C)// X が十分大きい(X ≥ T0)なら X! は必ず N で割り切るので,// 階乗操作で直接解 (剰余 0) となるll nextCost = curCost + C;if(nextCost < dp2[0]){dp2[0] = nextCost;pq.push({nextCost, 1, 0});}}}// 答えは dp2[0] に入っているはずcout << dp2[0] << "\n";return 0;}