【競技プログラミング】AtCoder Beginner Contest 369_E問題

# 問題
https://atcoder.jp/contests/abc369/tasks/abc369_e

# 既存投稿一覧ページへのリンク
[一覧ページ](https://qiita.com/o_chan_t/items/fc759695acfaef8df2cc)


# 解法手順1

1. **ワーシャルフロイド法**で全島間の最短距離を前計算
2. 各クエリに対して、指定された橋を**全ての可能な順序**で渡る場合を試す
3. 各順序において、橋の渡り方（どちら向きに渡るか）の全パターンを検討
4. 最小の移動時間を求める

## 1. 入力と前処理

- N個の島とM本の橋の情報を読み込む
- 各橋は島U_iと島V_iを結び、渡るのにT_i時間かかる
- Q個のクエリを読み込む（各クエリでは特定のK_i本の橋を渡る必要がある）

## 2. 全島間の最短距離計算

- 島間の直接移動時間を初期化（橋がない場合は∞）
- **ワーシャルフロイド法**を使って全ての島のペア間の最短距離を計算
  ```python
  for k in range(num_islands):
      for i in range(num_islands):
          for j in range(num_islands):
              distances[i][j] = min(distances[i][j], distances[i][k] + distances[k][j])
  ```

## 3. 各クエリの処理

各クエリに対して

1. 指定された橋を渡る**全ての可能な順序**を生成（順列）
   ```python
   for bridge_order in permutations(bridge_indices):
   ```

2. 各順序において、橋の渡り方（どちら向きに渡るか）の全パターンを検討
   - 各橋は島U→島Vまたは島V→島Uのどちらかの方向に渡れる
   - 2^K通りの渡り方がある（Kは渡る橋の数）
   - 各橋について両方向からの到達時間を追跡

3. 最短時間の計算
   - 現在位置から次の橋の始点までの移動時間
   - 橋を渡る時間
   - 最後の橋から島Nまでの移動時間
   を全て考慮して最小値を求める

## 4. 実装のポイント

- **状態追跡**: 
  - `time_to_u`と`time_to_v`: 島Uと島Vに到達する最小時間
  - `current_u`と`current_v`: 現在の位置

- **最適化**:
  - 橋を渡る順序を全て試す（順列）
  - 各橋の渡り方（方向）を考慮
  - 各ステップで最小時間を更新

- **最終結果**:
  - 全ての橋を渡る時間の合計 + 島間の移動時間の最小値



# ACコード1
```python:ac.py
import logging
from itertools import permutations

def setup_logger(debug_mode):
    logger = logging.getLogger(__name__)
    if not logger.handlers:  # ハンドラが存在しない場合のみ追加
        logger.setLevel(logging.DEBUG if debug_mode else logging.INFO)
        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        
        file_handler = logging.FileHandler('program_trace.log', encoding="utf8")
        file_handler.setFormatter(formatter)
        logger.addHandler(file_handler)
    
    logger.debug("ロガーのセットアップが完了しました。デバッグモード: %s", debug_mode)
    return logger

def io_func():
    # 入力処理
    num_islands, num_bridges = map(int, input().split())
    bridges = []
    for i in range(num_bridges):
        island_u, island_v, time = [int(i) - 1 for i in input().split()]
        time += 1  # 時間を調整
        bridges.append([island_u, island_v, time])
    
    num_queries = int(input())
    queries = []
    for _ in range(num_queries):
        num_bridges_to_cross = int(input())
        bridge_indices = [int(i) - 1 for i in input().split()]
        queries.append((num_bridges_to_cross, bridge_indices))
    
    return num_islands, num_bridges, bridges, num_queries, queries

def solve(num_islands, num_bridges, bridges, num_queries, queries):
    logger = setup_logger(False)
    logger.debug(f"島の数: {num_islands}, 橋の数: {num_bridges}")
    logger.debug(f"橋の情報: {bridges}")
    
    # 最短経路の初期化
    INF = 10 ** 18
    distances = [[INF] * num_islands for _ in range(num_islands)]
    for i in range(num_islands):
        distances[i][i] = 0
    
    # 橋の情報から直接つながっている島間の距離を設定
    for island_u, island_v, time in bridges:
        distances[island_u][island_v] = min(distances[island_u][island_v], time)
        distances[island_v][island_u] = min(distances[island_v][island_u], time)
    
    logger.debug(f"初期距離行列: {distances}")
    
    # ワーシャルフロイド法で全ての島間の最短距離を計算
    for k in range(num_islands):
        for i in range(num_islands):
            for j in range(num_islands):
                distances[i][j] = min(distances[i][j], distances[i][k] + distances[k][j])
    
    logger.debug(f"ワーシャルフロイド法適用後の距離行列: {distances}")
    
    results = []
    # 各クエリに対する処理
    for query_idx, (num_bridges_to_cross, bridge_indices) in enumerate(queries):
        logger.debug(f"クエリ {query_idx+1}: 渡る橋の数 {num_bridges_to_cross}, 橋のインデックス {bridge_indices}")
        
        # 渡る橋の総時間
        total_bridge_time = 0
        for bridge_idx in bridge_indices:
            total_bridge_time += bridges[bridge_idx][2]
        
        logger.debug(f"渡る橋の総時間: {total_bridge_time}")
        
        # 橋を渡る順序の全ての組み合わせを試す
        min_time = INF
        for bridge_order in permutations(bridge_indices):
            logger.debug(f"橋の順序: {bridge_order}")
            
            # 現在の位置と時間の状態を追跡
            time_to_u = 0  # 島Uに到達する最小時間
            time_to_v = 0  # 島Vに到達する最小時間
            current_u = 0  # 現在のU位置（最初は島1）
            current_v = 0  # 現在のV位置（最初は島1）
            
            logger.debug(f"初期状態: time_to_u={time_to_u}, time_to_v={time_to_v}, current_u={current_u}, current_v={current_v}")

            for bridge_idx in bridge_order:
                island_u, island_v, _ = bridges[bridge_idx]
                logger.debug(f"橋{bridge_idx}を検討中: 島{island_u}から島{island_v}への橋")
                
                # 次の橋の始点に移動する最小時間を計算
                next_time = min(time_to_u + distances[current_u][island_u], 
                               time_to_v + distances[current_v][island_u])
                logger.debug(f"  島{island_u}への移動時間計算: U({current_u})から={time_to_u + distances[current_u][island_u]}, V({current_v})から={time_to_v + distances[current_v][island_u]}, 最小値={next_time}")

                # 次の橋の終点に移動する最小時間を計算
                time_to_u = min(time_to_u + distances[current_u][island_v], 
                               time_to_v + distances[current_v][island_v])
                time_to_v = next_time
                
                # 現在位置を更新
                current_u = island_u
                current_v = island_v
            
            # 最後に島Nまでの時間を計算
            final_time_u = time_to_u + distances[current_u][num_islands - 1]
            final_time_v = time_to_v + distances[current_v][num_islands - 1]
            
            # 最小時間を更新
            min_time = min(min_time, final_time_u, final_time_v)
            
            logger.debug(f"この順序での最小時間: {min(final_time_u, final_time_v)}")
        
        # 結果に橋を渡る時間を加算
        result = min_time + total_bridge_time
        results.append(result)
        logger.debug(f"クエリ {query_idx+1} の結果: {result}")
    
    return results

def main():
    num_islands, num_bridges, bridges, num_queries, queries = io_func()
    results = solve(num_islands, num_bridges, bridges, num_queries, queries)
    
    # 結果を出力
    for result in results:
        print(result)

if __name__ == "__main__":
    main()

```