Lilliput Steps

小さな一歩から着実に. 数学やプログラミングのことを書きます.

ARC 016 C - ソーシャルゲーム

問題文 : ソーシャルゲーム


解法 :
$dp[S] :=$ 集合$S$ 内のアイテムを得ていたとして, 残りを全部揃えるための期待値, とすると, ガチャ$i$ から得ようとしている商品の集合$T$ ($T \cap S = \phi$) にたいして,

$dp[S] := \min \left (\dfrac{cost_i}{\sum_{j \in T}prob_{ij}} + \sum_{j \in T} dp[S \cup j] \times \dfrac{prob_{ij}}{\sum_{j \in T}prob_{ij}} \right )$

となる. 計算量は $O(2^{2n}mn)$ ≒ 4 * 10^7 程度なので間に合う.

コード :

#include <cstdio>
#include <vector>

using namespace std;

int N, M;
int C[4], cost[4];
int prob[4][10] = {{0}};
double memo[1025];

void binary(int x)
{
	for (int i = 5; i >= 0; i--) printf("%d ", x >> i & 1);
	printf("\n");
}

double f(int bits)
{
	if (memo[bits] >= 0) return (memo[bits]);
	if (bits == (1 << N) - 1) return (0);
	
	double ret = 1e18;
	for (int i = 1; i < (1 << N); i++){
		if (i != bits && (i & bits) == bits){
			for (int j = 0; j < M; j++){
				vector<pair<int, int> > tmp;
				int sum = 0;
				double ta;
				for (int k = 0; k < N; k++){
					if (bits >> k & 1) continue;
					if ((i >> k & 1) && !prob[j][k]) goto next;
					tmp.push_back(make_pair(prob[j][k], k));
					sum += prob[j][k];
				}
				ta = cost[j] * 100.0 / sum;
				for (int k = 0; k < tmp.size(); k++){
					ta += tmp[k].first * 1.0 / sum * f(bits | (1 << tmp[k].second));
				}
				ret = min(ret, ta);
				next:;
			}
		}
	}
	
	return (memo[bits] = ret);
}

int main()
{
	scanf("%d %d", &N, &M);
	
	for (int i = 0; i < M; i++){
		scanf("%d %d", C + i, cost + i);
		for (int j = 0; j < C[i]; j++){
			int x, y;
			scanf("%d %d", &x, &y);
			prob[i][--x] = y;
		}
	}
	
	for (int i = 0; i < 1 << N; i++) memo[i] = -1;
	
	printf("%.10lf\n", f(0));
	
	return (0);
}