問題一覧 > 通常問題

No.2909 Imaginary Summer

レベル : / 実行時間制限 : 1ケース 6.000秒 / メモリ制限 : 512 MB / 小数誤差許容問題 絶対誤差または相対誤差が$10^{-5}$ 以下。ただし、ジャッジ側の都合で500桁未満にしてください
タグ : / 解いたユーザー数 6
作問者 : ArcAkiArcAki / テスター : 👑 p-adicp-adic
1 ProblemId : 11401 / 自分の提出
問題文最終更新日: 2024-09-29 15:58:31

※注意※ PyPy3程度の速度の言語では実行制限時間に間に合わない可能性があります。C++等の高速な言語を推奨します。

問題文

白君はある地域へ $K$ 箇所の目的地を巡る旅行に出かけます。この地域は座標平面上に存在し、$N$ 駅からなる鉄道が走っていて、鉄道と徒歩で目的地とホテルを移動します。
徒歩で直接向かっても構いません。
鉄道路線は連結で $M$ 本走っており、路線 $i$ は駅 $S_i$ と駅 $T_i$ を双方向にまっすぐ直線状に結んでいます。
ホテルは点 $(P, Q)$ にあり、駅 $j$ はそれぞれ点 $(X_j, Y_j)$ に建設されていて、目的地 $k$ は点 $(A_k, B_k)$ です。
この旅行ではホテルを出発し今まで選んでいない目的地を一つ選択して訪れ、食事をしてからホテルに帰ることを $K$ 回繰り返します。
白君は少食なのでたとえ経路上に訪れていない他の目的地があってもご飯が食べられないため一回ホテルに戻ってから訪れる必要があります。
白君の歩行速度は1で鉄道での移動速度は V = $10^{18}$ です。
距離はユークリッド距離で定義され、乗り継ぎや乗り換えの時間は考慮しなくて良いものとします。
この旅行での移動時間の総和の最小値を求めてください。
サンプルを除く入力について、座標の入力は一様ランダムに生成され路線はランダムに生成されます。入力に関して詳細は以下の入力の説明を確認してください。

入力

$N\ M\ K$
$P\ Q$
$X_1\ Y_1$
$:$
$X_N\ Y_N$
$A_1\ B_1$
$:$
$A_K\ B_K$
V
$S_1\ T_1$
$:$
$S_M\ T_M$

入力は全て整数で与えられ以下の条件を満たします。サンプル以外の入力は以下の条件を満たすように下記のアルゴリズムで生成されます。
・$ 2 ≤ N ≤ 10^5$
・$ N-1 ≤ M ≤ \min(\frac{N × (N-1)}{2}, 10^5)$
・$ 1 ≤ K ≤ 10^5$
・$ -10^5 ≤ P, Q ≤ 10^5$
・$ -10^5 ≤ X_i, Y_i ≤ 10^5$
・$ (X_i, Y_i) ≠ (X_j, Y_j)\ (i ≠ j)$
・$ -10^5 ≤ A_i, B_i ≤ 10^5 $
・$ (A_i, B_i) ≠ (A_j, B_j)\ (i ≠ j)$
・$ (P, Q) ≠ (X_i, Y_i)$
・$ (P, Q) ≠ (A_i, B_i)$
・$ (X_i, Y_i) ≠ (A_j, B_j)$
・V $=10^{18}$
・$ 1 ≤ S_i, T_i ≤ N$
・$ S_i ≠ T_i $
・$ \{S_i, T_i\} ≠ \{S_j, T_j\}\ (i ≠ j)$
・鉄道路線は連結である。

入力生成方法は以下の通りです。

  1. まず一貫して利用する乱数生成器 $R$ のシード値を適当にシードを設定した別の乱数生成器 $R'$ で指定する。$N, M, K$ の決定以外の乱数やシャッフルには全てこの乱数生成器 $R$ を利用する。
  2. 条件を満たす $N, M, K$ を直接、もしくは乱数生成器 $R'$ による乱数で指定する。
  3. 次に乱数生成器 $R$ を用いてホテルの各座標 $P, Q$ の値を $-10^5$ 以上 $10^5$ 以下の値からそれぞれ一様ランダムに決定する。
  4. 駅の座標を $j = 1, 2, …, N$ の順に座標の候補 $(x, y)$ をここまでの生成でホテルや駅の座標として使用されていない座標になるまで上記と同様に生成することを繰り返して、最終的な $(x, y)$ の値を駅 $j$ の座標 $(X_j,\ Y_j)$とする。
  5. 目的地の座標も同様に $k = 1, 2, …, K$ の順にホテルや駅、目的地の座標として使用されていない値になるまで生成を繰り返しそれを目的地 $k$ の座標 $(A_k,\ B_k)$とする。
  6. V = $10^{18}$ と定める。
  7. 路線を以下の手続きで $M$ 個生成する。  
       
    • まず空の配列 $A$ と $1$ 以上 $N$ 以下の値を1つずつ持つ配列 $B$ を定める。
    •  
    • 次に全体として一様ランダムになるように $B$ をシャッフルする。
    •  
    • $B$ の末尾を削除しその値を $A$ に格納する。
    •  
    • そして $i = 1, 2, …, N-1$ において $A$ 内の要素から一様ランダムに一つ選んでこれを $a$ とし $B$ の末尾の要素を $b$ として、 $(S_i, T_i) = (a, b)$ と定め、配列 $B$ から $b$ を削除し配列 $A$ に $b$ を追加する。
    •  
    • 残りの $M-N+1$ 路線は $1$ 以上 $N$ 以下から一様ランダムに選ばれた2つの整数 $u, v$ について $u≠v$ かつすでに駅 $u$ と駅 $v$ を結ぶ路線がなければそれを新たな路線として追加し、 路線として $i$ 番目ならば $(S_i, T_i) = (u, v)$ と定めることを繰り返すことで決定する。
    •  

入力テストケースに関してシード値は解説ページで公開します。実際に生成に使用したプログラムコードのベースは以下にあります。
(実際はコメントアウトに記述した通りの操作のみを加えています。)
入力ジェネレータコードベースライン (Rust)


#[allow(unused_imports)]
use std::{collections::{HashSet, HashMap, BTreeSet, BTreeMap, BinaryHeap, VecDeque}, cmp::{Reverse, PartialOrd, Ordering, max, min}, mem::swap};

macro_rules! input {
    (source = $s:expr, $($r:tt)*) => {
        let mut iter = $s.split_whitespace();
        let mut next = || { iter.next().unwrap() };
        input_inner!{next, $($r)*}
    };
    ($($r:tt)*) => {
        let stdin = std::io::stdin();
        let mut bytes = std::io::Read::bytes(std::io::BufReader::new(stdin.lock()));
        let mut next = move || -> String{
            bytes
                .by_ref()
                .map(|r|r.unwrap() as char)
                .skip_while(|c|c.is_whitespace())
                .take_while(|c|!c.is_whitespace())
                .collect()
        };
        input_inner!{next, $($r)*}
    };
}

macro_rules! input_inner {
    ($next:expr) => {};
    ($next:expr, ) => {};

    ($next:expr, $var:ident : $t:tt $($r:tt)*) => {
        let $var = read_value!($next, $t);
        input_inner!{$next $($r)*}
    };
}

macro_rules! read_value {
    ($next:expr, ( $($t:tt),* )) => {
        ( $(read_value!($next, $t)),* )
    };

    ($next:expr, [ $t:tt ; $len:expr ]) => {
        (0..$len).map(|_| read_value!($next, $t)).collect::<\Vec<\_>>()
    };

    ($next:expr, chars) => {
        read_value!($next, String).chars().collect::<\Vec<\char>>()
    };

    ($next:expr, usize1) => {
        read_value!($next, usize) - 1
    };

    ($next:expr, $t:ty) => {
        $next().parse::<$t>().expect("Parse error")
    };
}

struct XorShiftStar{
    state: u64,
}

impl XorShiftStar{
    fn new(seed: u64)->Self{
        XorShiftStar{state: seed}
    }

    fn next(&mut self)->u64{
        let mut x = self.state;
        x ^= x << 13;
        x ^= x >> 7;
        x ^= x << 17;
        self.state = x;
        x.wrapping_mul(0x2545F4914F6CDD1D)
    }

    fn get(&mut self, l: usize, r: usize) -> usize {
        let range = r - l + 1;
        let num = (self.next() as usize) % (range) + l;
        num
    }

    fn shuffle<T>(&mut self, a: &mut Vec<T>){
        let n = a.len();
        for i in 0..n{
            let j = self.get(0, i);
            a.swap(i, j);
        }
    }

    fn choose<T>(&mut self, a: &Vec<T>)->T where T: Copy{
        let n = a.len();
        let p = self.get(0, n-1);
        a[p]
    }
}

fn main(){
    input!{
        c: u64,
    }
    //入力ごとに乱数シードを変更するため調整する
    let config1 = 0;
    let config2 = 0;
    //ここまで
    let mut pre_rng = XorShiftStar::new(config1*c+config2);
    for _ in 0..c{
        pre_rng.next();
    }
    let seed = (pre_rng.get(0, 199999) as u64+c*config2)%200000;
    let mut rng = XorShiftStar::new(seed);
    //ここは直接指定して操作するかpre_rngにより決定
    let n = pre_rng.get(2, 2);
    let m = pre_rng.get(n-1, 100000).min(n*(n-1)/2);
    let k = pre_rng.get(1, 100000);
    //ここまで。ここ以降は完全にrngのみで乱数を扱う。
    let mx = 200000i32;
    let h = 100000i32;
    let mut p = Vec::new();
    p.push(rng.get(0, mx as usize) as i32-h);
    p.push(rng.get(0, mx as usize) as i32-h);
    let mut used1 = HashSet::new();
    used1.insert((p[0], p[1]));
    let mut cnt = 0;
    let mut station = Vec::new();
    while cnt < n{
        let (u, v) = (rng.get(0, mx as usize) as i32-h, rng.get(0, mx as usize) as i32-h);
        if used1.contains(&(u, v)){continue}
        cnt += 1;
        used1.insert((u, v));
        station.push((u, v));
    }
    let mut t = Vec::new();
    cnt = 0;
    while cnt < k{
        let (u, v) = (rng.get(0, mx as usize) as i32-h, rng.get(0, mx as usize) as i32-h);
        if used1.contains(&(u, v)){continue}
        cnt += 1;
        used1.insert((u, v));
        t.push((u, v));
    }
    let z = 1_000_000_000_000_000_000i64;
    let mut leaf = Vec::new();
    for i in 0..n{
        leaf.push(i);
    }
    rng.shuffle(&mut leaf);
    let mut parent = Vec::new();
    let x = leaf.pop().unwrap();
    parent.push(x);
    let mut e = Vec::new();
    let mut used2 = HashSet::new();
    for _ in 1..n{
        let pre = rng.choose(&parent);
        let nex = leaf.pop().unwrap();
        e.push((pre, nex));
        parent.push(nex);
        used2.insert((pre, nex));
        used2.insert((nex, pre));
    }
    let mut cnt = n-1;
    while cnt < m{
        let (u, v) = (rng.get(0, n-1), rng.get(0, n-1));
        if u==v||used2.contains(&(u, v)){continue}
        used2.insert((u, v));
        used2.insert((v, u));
        e.push((u, v));
        cnt += 1;
    }
    assert!(n >= 2);
    assert!(n <= 100000);
    assert!(n-1 <= m);
    assert!(m <= n*(n-1)/2);
    assert!(m <= 100000);
    assert!(k >= 1);
    assert!(k <= 100000);
    assert!(p[0] <= h);
    assert!(p[0] >= -h);
    assert!(p[1] <= h);
    assert!(p[1] >= -h);
    assert_eq!(z, 1_000_000_000_000_000_000);
    println!("{} {} {}", n, m, k);
    println!("{} {}", p[0], p[1]);
    for &(u, v) in &station{
        assert!(u <= h);
        assert!(u >= -h);
        assert!(v <= h);
        assert!(v >= -h);
        println!("{} {}", u, v);
    }
    for &(u, v) in &t{
        assert!(u <= h);
        assert!(u >= -h);
        assert!(v <= h);
        assert!(v >= -h);
        println!("{} {}", u, v);
    }
    println!("{}", z);
    for &(u, v) in &e{
        assert!(u+1 >= 1);
        assert!(v+1 >= 1);
        assert!(u+1 <= n);
        assert!(v+1 <= n);
        println!("{} {}", u+1, v+1);
    }
    //ケース名はケースごとに直接指定するか渡された値を利用。シード値は出力から確認している。その後テストケース名から削除している。
    eprintln!("00_some_{}_seed_{}", c, seed);
}
    

 

出力

求める値を出力してください。writer解の値との絶対誤差または相対誤差が $10^{-5}$ 以下の時正解として扱われます。
最後に改行してください。

サンプル

サンプル1
入力
5 4 6
12 3
2 3
5 1
6 5
9 7
4 10
14 5
2 12
1 2
4 6
7 9
7 -2
1000000000000000000
1 2
3 2
5 3
3 4
出力
78.3769065542748


上の画像では駅と路線がグラフ、ホテルは緑の星、目的地がばつ印で表されています。
例えば目的地1へ行く場合は鉄道を経由しないで直接向かって帰るとき $4 \sqrt{2}$ で行き来できます。このように鉄道を使わない場合もあります。
座標は負の可能性もあることに注意してください。

サンプル2
入力
2 1 1
-100000 -100000
-100000 -99999
-100000 -99998
100000 100000
1000000000000000000
1 2
出力
565684.5965291844

鉄道に乗ることでわずかにでも短縮できる場合はそちらが優先されます。

サンプル3
入力
12 24 17
49568 69196
-98957 -87293
96282 -5498
30527 -30128
21828 -29401
-37956 94439
-78187 -73404
-25005 -38011
86936 40205
-8187 -34299
48841 96226
-29661 53258
-10550 -2726
38374 20431
-72654 -13277
-88480 -1097
44653 55980
13844 -15041
-57186 -70519
47119 -20186
-65670 18338
26320 -53469
-51293 14935
22716 86271
73126 -69136
30758 96068
-91332 -31362
3606 35963
-29619 -25760
-28544 55494
1000000000000000000
3 9
9 7
9 2
2 8
3 1
1 11
3 6
6 12
8 4
11 10
4 5
2 4
8 11
9 12
2 7
11 7
12 8
1 6
10 4
8 9
2 6
5 2
10 5
1 9
出力
1857858.1089122023

提出するには、Twitter 、GitHub、 Googleもしくは右上の雲マークをクリックしてアカウントを作成してください。