問題 1:数値変換プロセスと剰余定理
この問題では、巨大な数値 $n$ に対して特定の減算操作を繰り返す際の最終状態を求めます。操作の規則は、$n$ を $s$ で割った商と余りの和を次の $n$ とするものです。入力値が非常に大きくなる可能性があるため、単純なシミュレーションでは時間制限を超えてしまいます。
部分点対策:桁ごとのシミュレーション
まず、数値を文字列として受け取り、桁ごとに整数へ変換しながら処理を行うアプローチが考えられます。各桁の変換後に、値が閾値 $s$ を超えるたびに規定の減算処理を行います。この方法では、中間的な得点を得ることが可能です。
#include <iostream>
#include <string>
#include <vector>
using namespace std;
// 70 点相当のシミュレーション実装
void solve_simulation() {
int testCases;
cin >> testCases;
while (testCases--) {
int divisor;
string numStr;
cin >> divisor >> numStr;
long long currentVal = 0;
for (char c : numStr) {
currentVal = currentVal * 10 + (c - '0');
while (currentVal >= divisor) {
long long quotient = currentVal / divisor;
currentVal -= quotient * (divisor - 1);
}
}
cout << currentVal << endl;
}
}
満点対策:数論的アプローチ
数学的な分析を行うと、この操作は最終的に $n \bmod (s-1)$ の値に収束することがわかります。$n=0$ の場合は例外として 0 を返します。それ以外の場合、答えは $1 + ((n - 1) \bmod (s - 1))$ で求められます。入力値が巨大であるため、読み込み段階で $s-1$ に対する剰余を計算する最適化が必要です。
#include <cstdio>
#include <cctype>
using namespace std;
// 入力受け取りながら剰余を計算する関数
int readWithModulo(int mod, bool& isZero) {
int result = 0;
char ch = getchar();
int digitCount = 0;
while (!isdigit(ch)) ch = getchar();
while (isdigit(ch)) {
if (ch == '0' && digitCount == 0) {
isZero = true;
}
result = (result * 10 + (ch - '0')) % mod;
ch = getchar();
digitCount++;
}
return result;
}
int main() {
int t;
scanf("%d", &t);
while (t--) {
int s;
scanf("%d", &s);
bool zeroFlag = false;
// s-1 で剰余を取りながら読み込み
int nMod = readWithModulo(s - 1, zeroFlag);
if (zeroFlag && nMod == 0) {
puts("0");
} else {
// 数論的公式による計算
int ans = (nMod + s - 2) % (s - 1) + 1;
printf("%d\n", ans);
}
}
return 0;
}
問題 2:アイテム取得を伴う最短経路問題
二次元平面上に複数の地点が配置されており、スタート地点から特定のアイテムを取得しながらゴールを目指します。アイテムを使用するたびに移動速度が向上するため、どの順序でアイテムを取得するかが時間コストに影響します。
動的計画法による状態遷移
この問題は動的計画法(DP)を用いて解決できます。状態を $dp[i][j]$ と定義し、これは $i$ 番目の地点に到達し、スタート以外で $j$ 個のアイテムを使用した際の最小時間を表します。地点間の距離はマンハッタン距離で計算され、移動時間は距離を使用アイテム数(速度)で割った値となります。
遷移式は以下の通りです:
$dp[i][j] = \min(dp[i][j], dp[k][j-1] + \frac{dist(i, k)}{j})$
ここで、$k$ は直前に訪れた地点を示します。
#include <iostream>
#include <vector>
#include <cmath>
#include <iomanip>
#include <algorithm>
using namespace std;
struct Point {
int x, y;
};
// マンハッタン距離の計算
int calcDistance(const Point& a, const Point& b) {
return abs(a.x - b.x) + abs(a.y - b.y);
}
int main() {
int n, m, startX, startY, endX, endY;
cin >> n >> m >> startX >> startY >> endX >> endY;
// スタート地点のアイテムは必須使用ため、利用可能回数を減算
m--;
vector<Point> locations(n + 2);
// 0: ゴール地点,1: スタート地点,2..n+1: アイテム地点
locations[0] = {endX, endY};
locations[1] = {startX, startY};
for (int i = 2; i <= n + 1; ++i) {
cin >> locations[i].x >> locations[i].y;
}
int pointCount = n + 1;
// DP テーブルの初期化
vector<vector<double>> minTime(pointCount + 1, vector<double>(m + 1, 1e9));
minTime[1][0] = 0;
double globalMin = calcDistance(locations[0], locations[1]);
// アイテム不使用で各地点へ移動する初期コスト
for (int i = 2; i <= pointCount; ++i) {
minTime[i][0] = calcDistance(locations[1], i);
}
// DP 遷移処理
for (int used = 1; used <= m; ++used) {
for (int curr = 1; curr <= pointCount; ++curr) {
for (int prev = 1; prev <= pointCount; ++prev) {
if (curr == prev) continue;
double cost = minTime[prev][used - 1] + (double)calcDistance(locations[prev], locations[curr]) / used;
if (cost < minTime[curr][used]) {
minTime[curr][used] = cost;
}
}
// ゴールへの最終移動コストを更新
double toGoal = minTime[curr][used] + (double)calcDistance(locations[0], locations[curr]) / (used + 1);
if (toGoal < globalMin) {
globalMin = toGoal;
}
}
}
cout << fixed << setprecision(12) << globalMin << endl;
return 0;
}