Skip to content

Commit f1d50fc

Browse files
committed
atcoder A76 - River Crossing 座標圧縮 + 動的計画法(DP) + Fenwick Tree (BIT)
1 parent f0bca8b commit f1d50fc

7 files changed

Lines changed: 708 additions & 19 deletions

File tree

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"os"
7+
"sort"
8+
"strconv"
9+
)
10+
11+
const MOD = 1_000_000_007
12+
13+
type FenwickTree struct {
14+
n int
15+
data []int
16+
}
17+
18+
func NewFenwickTree(n int) *FenwickTree {
19+
return &FenwickTree{
20+
n: n,
21+
data: make([]int, n+2),
22+
}
23+
}
24+
25+
func (ft *FenwickTree) Add(i, x int) {
26+
i++
27+
for i <= ft.n+1 {
28+
ft.data[i] = (ft.data[i] + x) % MOD
29+
i += i & -i
30+
}
31+
}
32+
33+
func (ft *FenwickTree) Sum(i int) int {
34+
i++
35+
res := 0
36+
for i > 0 {
37+
res = (res + ft.data[i]) % MOD
38+
i -= i & -i
39+
}
40+
return res
41+
}
42+
43+
func (ft *FenwickTree) RangeSum(l, r int) int {
44+
return (ft.Sum(r) - ft.Sum(l-1) + MOD) % MOD
45+
}
46+
47+
func lowerBound(a []int, x int) int {
48+
return sort.Search(len(a), func(i int) bool { return a[i] >= x })
49+
}
50+
51+
func upperBound(a []int, x int) int {
52+
return sort.Search(len(a), func(i int) bool { return a[i] > x })
53+
}
54+
55+
func main() {
56+
scanner := bufio.NewScanner(os.Stdin)
57+
scanner.Split(bufio.ScanWords) // 単語(スペース区切り)単位で読み取り
58+
59+
readInt := func() int {
60+
scanner.Scan()
61+
n, _ := strconv.Atoi(scanner.Text())
62+
return n
63+
}
64+
65+
N := readInt()
66+
W := readInt()
67+
L := readInt()
68+
R := readInt()
69+
70+
X := make([]int, N)
71+
for i := 0; i < N; i++ {
72+
X[i] = readInt()
73+
}
74+
75+
// 全地点(スタート0、足場、ゴールW)をリストアップ
76+
positions := append([]int{0}, X...)
77+
positions = append(positions, W)
78+
sort.Ints(positions)
79+
80+
posIndex := make(map[int]int)
81+
for i, v := range positions {
82+
posIndex[v] = i
83+
}
84+
85+
n := len(positions)
86+
dp := make([]int, n)
87+
dp[0] = 1
88+
89+
ft := NewFenwickTree(n)
90+
ft.Add(0, 1)
91+
92+
for i := 1; i < n; i++ {
93+
cur := positions[i]
94+
left := cur - R
95+
right := cur - L
96+
97+
li := lowerBound(positions, left)
98+
ri := upperBound(positions, right) - 1
99+
100+
if li <= ri {
101+
val := ft.RangeSum(li, ri)
102+
dp[i] = val
103+
ft.Add(i, val)
104+
}
105+
}
106+
107+
fmt.Println(dp[n-1])
108+
}
109+
110+
// Go 解法で 20件の入力で ランタイムエラー(panic) が発生するとのこと、原因として考えられるのは以下のいずれかです:
111+
112+
// ❗️ 原因候補
113+
// bufio.Scanner の 2行しか読んでいない
114+
// 実際には N が大きくなると 複数行に分かれて入力される 可能性がある。
115+
// つまり "5 65 7 37" の次に "5 15 30 50 55" でなく、改行を含む複数行に X が分割されている可能性がある。
116+
// Xstr := strings.Fields(scanner.Text()) だけで全 N 要素を取得できない。
117+
118+
// ✅ 修正方針
119+
// N 個の X[i] を読み切るまで ループで scanner.Scan() を繰り返す
120+
// X := make([]int, N) を安全に埋める
121+
122+
// ✅ 修正点まとめ
123+
// 修正箇所 内容
124+
// scanner.Split(...) 単語単位で int を逐次読み取り可能に
125+
// readInt() 関数 scanner.Scan() + strconv.Atoi() のラッパー
126+
// X[i] = readInt() 必ず N 件読み切るようループで読み取り
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
const readline = require("readline");
2+
3+
const rl = readline.createInterface({
4+
input: process.stdin,
5+
output: process.stdout
6+
});
7+
8+
const MOD = 1_000_000_007;
9+
10+
rl.once("line", (line1) => {
11+
const [N, W, L, R] = line1.trim().split(" ").map(Number);
12+
rl.once("line", (line2) => {
13+
const X = line2.trim().split(" ").map(Number);
14+
15+
// 座標圧縮: 0 (スタート), 全ての足場, W (ゴール)
16+
const positions = [0, ...X, W];
17+
positions.sort((a, b) => a - b);
18+
const indexMap = new Map();
19+
positions.forEach((v, i) => indexMap.set(v, i));
20+
21+
const n = positions.length;
22+
const dp = Array(n).fill(0);
23+
dp[0] = 1;
24+
25+
// Fenwick Tree 実装
26+
class FenwickTree {
27+
constructor(n) {
28+
this.n = n;
29+
this.tree = Array(n + 2).fill(0);
30+
}
31+
32+
add(i, x) {
33+
i++;
34+
while (i <= this.n + 1) {
35+
this.tree[i] = (this.tree[i] + x) % MOD;
36+
i += i & -i;
37+
}
38+
}
39+
40+
sum(i) {
41+
i++;
42+
let res = 0;
43+
while (i > 0) {
44+
res = (res + this.tree[i]) % MOD;
45+
i -= i & -i;
46+
}
47+
return res;
48+
}
49+
50+
rangeSum(l, r) {
51+
return (this.sum(r) - this.sum(l - 1) + MOD) % MOD;
52+
}
53+
}
54+
55+
const ft = new FenwickTree(n);
56+
ft.add(0, 1);
57+
58+
for (let i = 1; i < n; i++) {
59+
const cur = positions[i];
60+
const left = cur - R;
61+
const right = cur - L;
62+
63+
// 二分探索で到達可能な範囲 [left, right] を index に変換
64+
let li = lowerBound(positions, left);
65+
let ri = upperBound(positions, right) - 1;
66+
67+
if (li <= ri) {
68+
const val = ft.rangeSum(li, ri);
69+
dp[i] = val;
70+
ft.add(i, val);
71+
}
72+
}
73+
74+
console.log(dp[n - 1]);
75+
rl.close();
76+
});
77+
});
78+
79+
// 二分探索: lower_bound
80+
function lowerBound(arr, x) {
81+
let left = 0, right = arr.length;
82+
while (left < right) {
83+
let mid = (left + right) >> 1;
84+
if (arr[mid] < x) left = mid + 1;
85+
else right = mid;
86+
}
87+
return left;
88+
}
89+
90+
// 二分探索: upper_bound
91+
function upperBound(arr, x) {
92+
let left = 0, right = arr.length;
93+
while (left < right) {
94+
let mid = (left + right) >> 1;
95+
if (arr[mid] <= x) left = mid + 1;
96+
else right = mid;
97+
}
98+
return left;
99+
}
100+
101+
102+
// ✅ 解法概要(JavaScript)
103+
// 出発点 0 と終点 W を含めた すべてのジャンプ可能な地点 を 昇順で管理。
104+
// ジャンプ可能距離 [L, R] に対して、ある地点 pos[i] に到達するために、直前のどの地点 pos[j] からジャンプすればいいかを 二分探索で特定。
105+
// 各地点の到達方法数 dp[i] を高速に合計管理するために、Binary Indexed Tree (Fenwick Tree) を使う。
106+
107+
// 🧠 考慮するポイント
108+
// JavaScript では大きな座標 (W ≤ 10^9) を扱うが、実際にジャンプできる点は最大でも N + 2 個しかないため、インデックス圧縮が使える。
109+
// dp[i]: i 番目の位置に到達する方法の数(mod 1e9+7)
110+
// dp[0] = 1(スタート地点)
111+
112+
// ⏱️ 計算量
113+
// 座標圧縮・二分探索・Fenwick Tree 操作:すべて O(N log N)
114+
// N ≤ 150000 でも十分間に合います。
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
const MOD = 1_000_000_007;
6+
7+
class FenwickTree
8+
{
9+
private int $n;
10+
private array $tree;
11+
12+
public function __construct(int $n)
13+
{
14+
$this->n = $n;
15+
$this->tree = array_fill(0, $n + 2, 0);
16+
}
17+
18+
public function add(int $i, int $x): void
19+
{
20+
$i++;
21+
while ($i <= $this->n + 1) {
22+
$this->tree[$i] = ($this->tree[$i] + $x) % MOD;
23+
$i += $i & -$i;
24+
}
25+
}
26+
27+
public function sum(int $i): int
28+
{
29+
$i++;
30+
$res = 0;
31+
while ($i > 0) {
32+
$res = ($res + $this->tree[$i]) % MOD;
33+
$i -= $i & -$i;
34+
}
35+
return $res;
36+
}
37+
38+
public function rangeSum(int $l, int $r): int
39+
{
40+
return ($this->sum($r) - $this->sum($l - 1) + MOD) % MOD;
41+
}
42+
}
43+
44+
function lowerBound(array $arr, int $x): int
45+
{
46+
$left = 0;
47+
$right = count($arr);
48+
while ($left < $right) {
49+
$mid = intdiv($left + $right, 2);
50+
if ($arr[$mid] < $x) {
51+
$left = $mid + 1;
52+
} else {
53+
$right = $mid;
54+
}
55+
}
56+
return $left;
57+
}
58+
59+
function upperBound(array $arr, int $x): int
60+
{
61+
$left = 0;
62+
$right = count($arr);
63+
while ($left < $right) {
64+
$mid = intdiv($left + $right, 2);
65+
if ($arr[$mid] <= $x) {
66+
$left = $mid + 1;
67+
} else {
68+
$right = $mid;
69+
}
70+
}
71+
return $left;
72+
}
73+
74+
function main(): void
75+
{
76+
[$N, $W, $L, $R] = array_map('intval', explode(' ', trim(fgets(STDIN))));
77+
$Xs = array_map('intval', explode(' ', trim(fgets(STDIN))));
78+
79+
// 圧縮対象: 0, 足場, W
80+
$positions = array_merge([0], $Xs, [$W]);
81+
sort($positions);
82+
$n = count($positions);
83+
84+
// 位置→インデックスマップ(未使用でもOK)
85+
$posToIndex = [];
86+
foreach ($positions as $i => $val) {
87+
$posToIndex[$val] = $i;
88+
}
89+
90+
$dp = array_fill(0, $n, 0);
91+
$dp[0] = 1;
92+
93+
$ft = new FenwickTree($n);
94+
$ft->add(0, 1);
95+
96+
for ($i = 1; $i < $n; $i++) {
97+
$cur = $positions[$i];
98+
$left = $cur - $R;
99+
$right = $cur - $L;
100+
101+
$li = lowerBound($positions, $left);
102+
$ri = upperBound($positions, $right) - 1;
103+
104+
if ($li <= $ri) {
105+
$dp[$i] = $ft->rangeSum($li, $ri);
106+
$ft->add($i, $dp[$i]);
107+
}
108+
}
109+
110+
echo $dp[$n - 1] . PHP_EOL;
111+
}
112+
113+
main();
114+
115+
116+
// 高速化と厳密な型指定を意識しつつ、座標圧縮 + 動的計画法 + Fenwick Tree(BIT) を用いて、制約(最大 15 万件)に対応しています。
117+
118+
// ✅ 解法方針(再掲)
119+
// ジャンプ可能地点:0, 全ての足場 X[i], ゴール地点 W
120+
// 到達可能な方法数:dp[i](positions[i] に到達する方法の数)
121+
// 区間加算高速化:Fenwick Tree(BIT)で dp[li..ri] を合計
122+
// 各 dp[i] を高速に更新して最終地点の dp を出力
123+
124+
// 📌 型指定まとめ
125+
// 変数・関数 型 用途
126+
// $dp array<int> 各位置の到達通り数
127+
// FenwickTree::add() (int, int): void 指定位置に加算
128+
// FenwickTree::sum() (int): int 前からの累積和
129+
// FenwickTree::rangeSum() (int, int): int 区間和(高速)
130+
// lowerBound() (array<int>, int): int 二分探索:最初に x 以上の位置
131+
// upperBound() (array<int>, int): int 二分探索:最初に x より大きい位置

0 commit comments

Comments
 (0)