AOJ1040 Chocolate with Heart Marks
問題リンク Chocolate with Heart Marks
- 解法
当初、全探索+枝刈りで頑張っていたのですがどうしてもTLEが解けなくて諦めていました。
何かのきっかけで「最小シュタイナー木」というものの存在を知りました。シュタイナー木とはグラフGと、頂点の部分集合Sが与えられたとき、Sを全て連結にするような木のことです。最小シュタイナー木とは、シュタイナー木の中で辺の重みの総計が最も小さいもののことです。ハートのついている箇所を全て結び、かつ少ないコストで求めたいという本問にドンピシャですね。
スパゲッティさんのコードをJava用に書きなおして解きました。各グリッドが頂点に対応し、辺はグリッドの隣接点を結び、コストは1です。最小シュタイナー木の結果+1の数だけチョコレートを食べることになるので、H*Wからこれを引いたものが答えとなります。
- ソース
import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Scanner; //Chocolate with Heart Marks public class AOJ1040 { int minimumSteinerTree(List<Integer> vs, int[][] e, int n){ int K = vs.size(), INF = 1<<29; if(K<=1)return 0; int[][] wf = new int[n][n]; for(int i=0;i<n;i++)for(int j=0;j<n;j++)wf[i][j]=e[i][j]; for(int i=0;i<n;i++)wf[i][i]=0; for(int k=0;k<n;k++)for(int i=0;i<n;i++)for(int j=0;j<n;j++)wf[i][j]=Math.min(wf[i][j], wf[i][k]+wf[k][j]); int[][] dp = new int[1<<K][n]; for(int[]d:dp)Arrays.fill(d, INF); for(int p=0;p<K;p++)for(int q=0;q<n;q++)dp[1<<p][q]=wf[vs.get(p)][q]; for(int S=1;S<1<<K;S++){ if((S&(S-1))==0)continue; for(int p=0;p<n;p++)for(int sub=0;sub<S;sub++){ if((S|sub)==S){ dp[S][p] = Math.min(dp[S][p], dp[sub][p]+dp[S-sub][p]); } } for(int p=0;p<n;p++)for(int q=0;q<n;q++){ dp[S][p] = Math.min(dp[S][p], dp[S][q]+wf[q][p]); } } int res = INF; for(int S=0;S<1<<K;S++)for(int q=0;q<n;q++){ res = Math.min(res, dp[S][q] + dp[((1<<K)-1)-S][q]); } return res; } void run(){ Scanner sc = new Scanner(System.in); int[][] d = {{-1,0},{0,1},{1,0},{0,-1}}; for(;;){ int h = sc.nextInt(), w = sc.nextInt(); if((h|w)==0)break; int[][] e = new int[h*w][h*w]; for(int[]a:e)Arrays.fill(a, 1<<29); List<Integer> vs = new ArrayList<Integer>(); for(int i=0;i<h;i++)for(int j=0;j<w;j++){ if(sc.nextInt()==1)vs.add(i*w+j); for(int k=0;k<4;k++){ int ni = i+d[k][0], nj = j+d[k][1]; if(0<=ni&&ni<h&&0<=nj&&nj<w)e[i*w+j][ni*w+nj]=1; } } System.out.println(h*w-minimumSteinerTree(vs, e, h*w)-1); } } public static void main(String[] args) { new AOJ1040().run(); } }