Open tipstar0125 opened 8 months ago
相互情報量
参考:https://kiri8128.hatenablog.com/entry/2024/02/19/211740
事前分布P(x)、事後分布Q(x)としたときの情報量は下式で表される。
今までの計測(占い)によって得られた分布より、任意のkを選んだときの油田の数を求める。 これは、各候補となる盤面に対して(盤面の確率)x(kマスの油田数)を求めて、平均した値と期待できる。 この値がクエリを投げた結果の戻り値だと仮定して、ベイズで仮の事後分布を求めて、情報量を求める式より、値を得る。 これをコストで割った値が最大になるものを選ぶことで、コストを抑えることができる。
ただし、kの数やパターンを考えると計算が間に合わないので、パターンを絞る工夫が必要。
【結論】 なんか違いそう。。。 もう少し式レベルで考えないといけない。
その他参考 terryさん:https://www.terry-u16.net/entry/ahc030 あぷりしあさん(相互情報量):https://qiita.com/aplysia/items/29a4fb4573fc1b8dec79?utm_campaign=post_article&utm_medium=twitter&utm_source=twitter_share AHCラジオ:https://www.youtube.com/watch?v=YvCYsiu-TQs&t=6s Write解:https://atcoder.jp/contests/ahc030/submissions/50443474
M=2 eps=0.2のときに10秒かかる。。。 ※最初は固定占いで相互情報量の多いものを選び、3bitより小さくなったら、山登り占い。
#![allow(non_snake_case)]
#![allow(unused_imports)]
#![allow(unused_macros)]
#![allow(clippy::comparison_chain)]
#![allow(clippy::nonminimal_bool)]
#![allow(clippy::neg_multiply)]
#![allow(clippy::type_complexity)]
#![allow(clippy::needless_range_loop)]
#![allow(dead_code)]
use std::{
cmp::Reverse,
collections::{BTreeMap, BTreeSet, BinaryHeap, HashSet, VecDeque},
};
use im_rc::HashMap;
use itertools::Itertools;
use proconio::{
input,
marker::{Chars, Usize1},
};
use rand::prelude::*;
use superslice::Ext;
fn main() {
let start = std::time::Instant::now();
let mut stdin =
proconio::source::line::LineSource::new(std::io::BufReader::new(std::io::stdin()));
macro_rules! input(($($tt:tt)*) => (proconio::input!(from &mut stdin, $($tt)*)));
input! {
N: usize,
M: usize,
eps: f64,
}
let mut minos = vec![];
for _ in 0..M {
input! {
d: usize,
coords: [(usize, usize); d]
}
let mut coord_diff = vec![];
let mut height = 0;
let mut width = 0;
for coord in coords.iter() {
let row = coord.0;
let col = coord.1;
height = height.max(row + 1);
width = width.max(col + 1);
coord_diff.push(CoordDiff::new(row as isize, col as isize));
}
let mino = Mino {
coord_diff,
height,
width,
};
minos.push(mino);
}
// Local
input! {
_ps: [(usize, usize); M],
ans: [[i16; N]; N],
es: [f64; 2*N*N]
}
let ans = ans.into_iter().flatten().collect_vec();
let ans = DynamicMap2d::new(ans, N);
let candidate_mino_coords = make_candidate_mino_coords(N, &minos);
let boards = make_boards(N, &minos, &candidate_mino_coords);
let mut mino_num = 0;
for mino in minos.iter() {
mino_num += mino.coord_diff.len();
}
let mut turn = 0;
let mut cost = 0.0_f64;
let mut rng = rand_chacha::ChaCha20Rng::seed_from_u64(1);
let board_num = boards.len();
let mut prob = vec![1.0 / (board_num as f64); board_num];
let mut log2 = vec![];
for i in 0..=1e4 as usize + 1 {
let x = i as f64 / 1e4;
log2.push(x.log2());
}
let climbing_iteration_num = 1000;
let mut fixed_fortune_coords = vec![];
for sep_num in 2..3 {
for i in 0..N {
if i + N / sep_num >= N {
continue;
}
for j in 0..N {
if j + N / sep_num >= N {
continue;
}
let mut coords = vec![];
for k in 0..N / sep_num {
for l in 0..N / sep_num {
coords.push(Coord::new(i + k, j + l));
}
}
fixed_fortune_coords.push((coords.len(), coords));
}
}
}
eprintln!("fixed_fortune_num: {}", fixed_fortune_coords.len());
let info_amount_threshold = 3.0;
while !fixed_fortune_coords.is_empty() {
let mut best_score = 0.0;
let mut best_k = 0;
let mut best_coords = vec![];
let mut remove_index = vec![];
for i in 0..fixed_fortune_coords.len() {
let mut cnt = vec![0_i16; board_num];
let (k, coords) = fixed_fortune_coords[i].clone();
for (c, b) in cnt.iter_mut().zip(boards.iter()) {
for coord in coords.iter() {
*c += b[*coord];
}
}
let score = calc_mutual_information(k, eps, &prob, &cnt, mino_num, &log2);
if score < info_amount_threshold {
remove_index.push(i);
}
if score > best_score {
best_score = score;
best_k = k;
best_coords = coords.clone();
}
}
let k = best_k;
let coords = best_coords;
if best_score < info_amount_threshold {
break;
}
remove_index.sort();
remove_index.reverse();
for idx in remove_index.iter() {
fixed_fortune_coords.remove(*idx);
}
let ret = query(&coords, &ans, eps, &es, &mut turn, &mut cost);
make_query(&coords);
// input! {ret:u8};
for i in 0..board_num {
let mut cnt = 0;
for coord in coords.iter() {
cnt += boards[i][*coord];
}
prob[i] *= likelihood(k, eps, cnt, ret);
}
normalize(&mut prob);
let mut p_cnt = 0;
for p in prob.iter() {
if *p > 1e-4 {
p_cnt += 1;
}
}
if p_cnt * climbing_iteration_num < 1e5 as usize {
break;
}
}
eprintln!("fixed_fortune_num: {}", fixed_fortune_coords.len());
loop {
let coords = decide_fortune(
N,
eps,
&mut rng,
climbing_iteration_num,
&prob,
&boards,
mino_num,
&log2,
);
let k = coords.len();
// eprintln!("{}", k);
let ret = query(&coords, &ans, eps, &es, &mut turn, &mut cost);
make_query(&coords);
// input! {ret:u8};
for i in 0..board_num {
let mut cnt = 0;
for coord in coords.iter() {
cnt += boards[i][*coord];
}
prob[i] *= likelihood(k, eps, cnt, ret);
}
normalize(&mut prob);
let mut mx = 0.0;
let mut idx = 0;
for (i, &p) in prob.iter().enumerate() {
if mx < p {
mx = p;
idx = i;
}
}
// let mut pp = vec![];
// for (i, p) in prob.iter().enumerate() {
// if *p > 0.01 {
// pp.push((i, *p));
// }
// }
// eprintln!("{:?}", pp);
if mx > 0.8 {
make_answer(&boards[idx]);
let ret = eval(&boards[idx], &ans);
eprintln!("{idx}");
// input! {ret:u8};
if ret == 1 {
break;
}
cost += 1.0;
prob[idx] = 0.0;
}
}
let score = (1e6 * cost.max(1.0 / N as f64)).round() as usize;
eprintln!("Turn: {}", turn as f64 / (2.0 * N as f64 * N as f64));
eprintln!("Cost: {}", cost);
eprintln!("Score: {score}");
#[allow(unused_mut, unused_assignments)]
let mut elapsed_time = start.elapsed().as_micros() as f64 * 1e-6;
#[cfg(feature = "local")]
{
eprintln!("Local Mode");
elapsed_time *= 0.55;
}
eprintln!("Elapsed: {}", (elapsed_time * 1000.0) as usize);
}
fn make_candidate_mino_coords(N: usize, minos: &[Mino]) -> Vec<Vec<Coord>> {
let mut ret = vec![];
for mino in minos.iter() {
let mut cands = vec![];
for i in 0..N - mino.height + 1 {
for j in 0..N - mino.width + 1 {
let pos = Coord::new(i, j);
cands.push(pos);
}
}
ret.push(cands);
}
ret
}
fn make_boards(
N: usize,
minos: &[Mino],
candidate_mino_coords: &[Vec<Coord>],
) -> Vec<DynamicMap2d<i16>> {
let M = minos.len();
let mut ret = vec![];
let mut Q = VecDeque::new();
Q.push_back((0, DynamicMap2d::new(vec![0_i16; N * N], N)));
while let Some((cnt, B)) = Q.pop_front() {
if cnt == M {
ret.push(B.clone());
continue;
}
for &pos in candidate_mino_coords[cnt].iter() {
let mut nB = B.clone();
for &coord_diff in minos[cnt].coord_diff.iter() {
let nxt = pos + coord_diff;
nB[nxt] += 1;
}
Q.push_back((cnt + 1, nB));
}
}
ret
}
#[allow(clippy::too_many_arguments)]
fn decide_fortune(
N: usize,
eps: f64,
rng: &mut rand_chacha::ChaCha20Rng,
iteration_num: usize,
prob: &[f64],
boards: &[DynamicMap2d<i16>],
mino_num: usize,
log2: &[f64],
) -> Vec<Coord> {
let mut fortune_map = DynamicMap2d::new(vec![true; N * N], N);
let mut best_score = 0.0;
let mut k = (N * N) as i16;
let L = boards.len();
let mut cnt = vec![0_i16; L];
for (c, b) in cnt.iter_mut().zip(boards) {
for i in 0..N {
for j in 0..N {
let coord = Coord::new(i, j);
if fortune_map[coord] {
*c += b[coord];
}
}
}
}
for _ in 0..iteration_num {
let i = rng.gen_range(0, N);
let j = rng.gen_range(0, N);
let coord = Coord::new(i, j);
let delta = if fortune_map[coord] { -1 } else { 1 };
fortune_map[coord] = !fortune_map[coord];
for (i, board) in boards.iter().enumerate() {
cnt[i] += board[coord] * delta;
}
k += delta;
let score = calc_mutual_information(k as usize, eps, prob, &cnt, mino_num, log2);
if score > best_score {
best_score = score;
} else {
let delta = if fortune_map[coord] { -1 } else { 1 };
fortune_map[coord] = !fortune_map[coord];
for (i, board) in boards.iter().enumerate() {
cnt[i] += board[coord] * delta;
}
k += delta;
}
}
let mut fortune_coord = vec![];
for i in 0..N {
for j in 0..N {
let coord = Coord::new(i, j);
if fortune_map[coord] {
fortune_coord.push(coord);
}
}
}
fortune_coord
}
fn calc_mutual_information(
k: usize,
eps: f64,
prob: &[f64],
cnt: &[i16],
mino_num: usize,
log2: &[f64],
) -> f64 {
let mean_max = (k as f64 - mino_num as f64) * eps + (mino_num as f64) * (1.0 - eps);
let std_dev = ((k as f64) * eps * (1.0 - eps)).sqrt();
let query_result_num = (mean_max + 3.0 * std_dev) as usize;
let mut query_result_p_vec = vec![0.0; query_result_num + 1];
let prune_threshold = 1e-4; // <0.01%
for (&p, &c) in prob.iter().zip(cnt) {
if p < prune_threshold {
continue;
}
let mean = (k as f64 - c as f64) * eps + (c as f64) * (1.0 - eps);
let lower = (mean - 3.0 * std_dev).max(0.0) as usize;
let upper = ((mean + 3.0 * std_dev) as usize).min(query_result_num);
for query_result in lower..=upper {
query_result_p_vec[query_result] += p * likelihood(k, eps, c, query_result);
}
}
let mut ret = 0.0;
for (query_result, query_result_p) in query_result_p_vec.iter().enumerate() {
if *query_result_p < prune_threshold {
continue;
}
for (&p, &c) in prob.iter().zip(cnt) {
if p < prune_threshold {
continue;
}
let likelihood = likelihood(k, eps, c, query_result);
if likelihood < prune_threshold {
continue;
}
let likelihood_usize = (likelihood * 1e4) as usize;
let query_result_p_usize = (query_result_p * 1e4) as usize;
ret += likelihood * p * (log2[likelihood_usize] - log2[query_result_p_usize]);
}
}
ret * (k as f64).sqrt()
}
#[allow(clippy::approx_constant)]
fn normal_cdf(x: f64, mean: f64, std_dev: f64) -> f64 {
0.5 * (1.0 + libm::erf((x - mean) / (std_dev * 1.41421356237)))
}
fn probability_in_range(mean: f64, std_dev: f64, a: f64, b: f64) -> f64 {
if mean < a {
return probability_in_range(mean, std_dev, 2.0 * mean - b, 2.0 * mean - a);
}
let p_a = normal_cdf(a, mean, std_dev);
let p_b = normal_cdf(b, mean, std_dev);
p_b - p_a
}
fn likelihood(k: usize, eps: f64, cnt: i16, res: usize) -> f64 {
let mean = (k as f64 - cnt as f64) * eps + (cnt as f64) * (1.0 - eps);
let std_dev = ((k as f64) * eps * (1.0 - eps)).sqrt();
if res == 0 {
probability_in_range(mean, std_dev, -1e10, res as f64 + 0.5)
} else {
probability_in_range(mean, std_dev, res as f64 - 0.5, res as f64 + 0.5)
}
}
fn normalize(prob: &mut [f64]) {
let s = prob.iter().sum::<f64>();
assert!(s >= 0.0);
for p in prob.iter_mut() {
*p /= s;
}
}
#[derive(Debug)]
struct Mino {
coord_diff: Vec<CoordDiff>,
height: usize,
width: usize,
}
fn query(
coords: &[Coord],
ans: &DynamicMap2d<i16>,
eps: f64,
es: &[f64],
turn: &mut usize,
cost: &mut f64,
) -> usize {
let k = coords.len() as f64;
*turn += 1;
*cost += 1.0 / k.sqrt();
if k == 1.0 {
return ans[coords[0]] as usize;
}
let mut vs = 0.0;
for coord in coords.iter() {
vs += ans[*coord] as f64;
}
let mean = (k - vs) * eps + vs * (1.0 - eps);
let std = (k * eps * (1.0 - eps)).sqrt();
let ret = mean + std * es[*turn];
let ret = ret.round() as usize;
ret.max(0)
}
fn make_query(coords: &[Coord]) {
let mut v = vec![];
v.push(coords.len());
for coord in coords.iter() {
v.push(coord.row);
v.push(coord.col);
}
let mut query = "q ".to_string();
query += v.iter().join(" ").as_str();
println!("{query}");
}
fn make_answer(a: &DynamicMap2d<i16>) {
let N = a.size;
let mut coords = vec![];
for i in 0..N {
for j in 0..N {
let coord = Coord::new(i, j);
if a[coord] > 0 {
coords.push(coord);
}
}
}
let mut v = vec![];
v.push(coords.len());
for coord in coords.iter() {
v.push(coord.row);
v.push(coord.col);
}
let mut ans = "a ".to_string();
ans += v.iter().join(" ").as_str();
println!("{ans}");
}
fn eval(a: &DynamicMap2d<i16>, ans: &DynamicMap2d<i16>) -> u8 {
let N = a.size;
for i in 0..N {
for j in 0..N {
let pos = Coord::new(i, j);
if ans[pos] != a[pos] {
return 0;
}
}
}
1
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Coord {
row: usize,
col: usize,
}
impl Coord {
pub fn new(row: usize, col: usize) -> Self {
Self { row, col }
}
pub fn in_map(&self, height: usize, width: usize) -> bool {
self.row < height && self.col < width
}
pub fn to_index(&self, width: usize) -> CoordIndex {
CoordIndex(self.row * width + self.col)
}
}
impl std::ops::Add<CoordDiff> for Coord {
type Output = Coord;
fn add(self, rhs: CoordDiff) -> Self::Output {
Coord::new(
self.row.wrapping_add_signed(rhs.dr),
self.col.wrapping_add_signed(rhs.dc),
)
}
}
#[derive(Debug, Clone, Copy)]
pub struct CoordDiff {
dr: isize,
dc: isize,
}
impl CoordDiff {
pub const fn new(dr: isize, dc: isize) -> Self {
Self { dr, dc }
}
}
pub const ADJ: [CoordDiff; 4] = [
CoordDiff::new(1, 0),
CoordDiff::new(!0, 0),
CoordDiff::new(0, 1),
CoordDiff::new(0, !0),
];
pub struct CoordIndex(pub usize);
impl CoordIndex {
pub fn new(index: usize) -> Self {
Self(index)
}
pub fn to_coord(&self, width: usize) -> Coord {
Coord {
row: self.0 / width,
col: self.0 % width,
}
}
}
#[derive(Debug, Clone)]
pub struct DynamicMap2d<T> {
pub size: usize,
map: Vec<T>,
}
impl<T> DynamicMap2d<T> {
pub fn new(map: Vec<T>, size: usize) -> Self {
assert_eq!(size * size, map.len());
Self { size, map }
}
}
impl<T> std::ops::Index<Coord> for DynamicMap2d<T> {
type Output = T;
#[inline]
fn index(&self, coordinate: Coord) -> &Self::Output {
&self[coordinate.to_index(self.size)]
}
}
impl<T> std::ops::IndexMut<Coord> for DynamicMap2d<T> {
#[inline]
fn index_mut(&mut self, coordinate: Coord) -> &mut Self::Output {
let size = self.size;
&mut self[coordinate.to_index(size)]
}
}
impl<T> std::ops::Index<CoordIndex> for DynamicMap2d<T> {
type Output = T;
fn index(&self, index: CoordIndex) -> &Self::Output {
unsafe { self.map.get_unchecked(index.0) }
}
}
impl<T> std::ops::IndexMut<CoordIndex> for DynamicMap2d<T> {
#[inline]
fn index_mut(&mut self, index: CoordIndex) -> &mut Self::Output {
unsafe { self.map.get_unchecked_mut(index.0) }
}
}
Write解:https://atcoder.jp/contests/ahc030/submissions/50443474
高速化の工夫
配置候補の尤度計算
ローカルテスタ、コマンド
cargo run -r --manifest-path tools/Cargo.toml --bin tester cargo run -r --bin c < in > out
問題:https://atcoder.jp/contests/ahc030/tasks/ahc030_a 解答例:https://atcoder.jp/contests/ahc030/submissions/50496667
ベイズの定理より P(x|R)∝P(R|x)
x: 盤面の確率 R: 測定結果
測定結果Rのときの盤面xの確率を直接求めることは難しいが、 盤面xであったときの、測定結果Rの確率を求めることで、P(x|R)を推定できる。
参考1: https://qiita.com/aplysia/items/c3f2111110ac5043710a 参考1: https://bowwowforeach.hatenablog.com/entry/2023/08/24/205427?_gl=1*1rve9o5*_gcl_au*MTE5MTM3NjYzLjE2OTEzMjM4ODU