Loading

题目归档 #3

目录

  • [SCOI2008] 奖励关
  • [HAOI2010] 软件安装
  • [HDU3853] LOOPS

[SCOI2008] 奖励关

一道综合 状态压缩 和 概率期望 的 dp 好题。

题意

系统将依次随机抛出 \(k\) 次宝物,每次你都可以选择吃或者不吃(必须在抛出下一个宝物之前做出选择,且现在决定不吃的宝物以后也不能再吃)。

宝物一共有 \(n\) 种,系统每次抛出这 \(n\) 种宝物的概率都相同且相互独立。获取第 \(i\) 种宝物将得到\(P_i\) 分,但并不是每种宝物都是可以随意获取的。第 \(i\) 种宝物有一个前提宝物集合 \(S_i\)。只有当\(S_i\) 中所有宝物都至少吃过一次,才能吃第 \(i\) 种宝物。\(P_i\) 可以是负数。

假设你采取最优策略,平均情况你一共能在奖励关得到多少分值?

  • \(k \leq 100,n \leq 15\)

解题

看到 \(n \leq 15\),第一反应必然是状态压缩。

于是想到令 \(f[i][j]\) 表示当前在第 \(i\) 次选择,前面 \(i-1\) 次及以前选择吃的宝物的集合在二进制下为 \(j\)

我们可以开始写转移方程。但是这会遇到一个问题:你并不知道用 \(i-1\) 次能否达到 \(j\) 这个状态。为了避免程序复杂,我们可以考虑期望的一个惯用套路—— 逆推

\(f[i][j]\) 表示当前在第 \(i\) 次选择,前面 \(i-1\) 次及以前选择吃的宝物的集合在二进制下为 \(j\) 时,第 \(i+1\) 到第 \(K\) 次选择的最优策略下期望得分。

第三维枚举当前第 \(i\) 次的宝物 \(t\)。如果状态 \(j\) 满足 \(t\) 的相关条件,正着看,我们就可以选择 \(t\) 了。此时有转移方程(\(val[t]\) 表示 \(t\) 的分值):

\(f[i][j] += max(f[i + 1][j], f[i + 1][j | (1 << (t-1))] + val[t]) / N\)

如果状态 \(j\) 不满足 \(t\) 的条件,那就没有办法选 \(t\) 咯。这个时候只有继承 \(i+1\) 的答案,因为在无法选择的情况下, \(1 \sim i-1\)\(1 \sim i\) 的选择集合一定都一样了。因此此时有转移方程:

\(f[i][j] += f[i+1][j] / N\)

最后的答案,想必就是 \(f[1][0]\) 了吧。

程序

#include <iostream>
#include <cstring>
#include <cstdio>

#define Maxn 15
#define Maxk 105

using namespace std;

int read() {
	int x = 0, f = 1;
	char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1;
		c = getchar();
	}
	while('0' <= c && c <= '9') {
		x = x * 10 + c - '0';
		c = getchar();
	}
	return x * f;
}

int K, N, val[Maxn], req[Maxn];
double f[Maxk][1 << Maxn];

int main() {
	K = read(); N = read();
	for(int i = 1; i <= N; ++i) {
		val[i] = read();
		int q = read();
		while(q) {
			req[i] += (1 << (q - 1));
			q = read();
		}
	}
	for(int i = K; i >= 1; --i) {
		for(int j = 0; j < (1 << N); ++j) {
			for(int t = 1; t <= N; ++t) {
				int nowt = (1 << (t - 1));
				if((j | req[t]) == j) {
					if(j & nowt) f[i][j] += max(f[i + 1][j], f[i + 1][j] + val[t]);
					else f[i][j] += max(f[i + 1][j], f[i + 1][j | nowt] + val[t]);
				}
				else f[i][j] += f[i + 1][j];
 			}
			f[i][j] = f[i][j] * 1.0 / N;
		}
	}
	printf("%.6lf", f[1][0]);
	return 0;
} 

[HAOI2010] 软件安装

一道比较套路的树上背包型 dp 题目,写起来有点小麻烦。可以视作「选课」那道题的加强版。

题意

\(N\) 个软件。软件 \(i\),它要占用 \(W_i\) 的磁盘空间,价值为 \(V_i\) 。从中选择一些软件安装到一台磁盘容量为 \(M\) 计算机上,使得这些软件的价值尽可能大。

软件之间存在依赖关系,即软件 \(i\) 只有在安装了软件 \(D_i\) 并且 \(D_i\) 正常工作的情况下才能正确工作。一个软件最多依赖另外一个软件。无法正常工作的软件可以直接无视。

求最大软件价值和。

  • \(N \leq 100,M \leq 500\)

解题

首先明显看出,这是一棵由「内向树」和「树」组成的森林。显然必须把那些环干掉,考虑到一个环必须一起选,我们可以找出所有的环并把他们的 \(W,V\) 都相加。缩为一个点后分配一个编号,与原先连到这个环上的点相连。建完新图后跑一边树上背包即可。

找环可以用 tarjan,这里我使用的是 topsort。

程序

#include <iostream>
#include <cstring>
#include <cstdio>

#define Maxn 110
#define Maxm 510

using namespace std;

const int S = 0;

int read() {
	int x = 0, f = 1;
	char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1;
		c = getchar();
	}
	while('0' <= c && c <= '9') {
		x = x * 10 + c - '0';
		c = getchar();
	}
	return x * f;
}

int N, M, W[Maxn], V[Maxn], D[Maxn], deg[Maxn];

struct Edge {
	int next, to;
}
edge[Maxn * 2];
int head[Maxn], edge_num;

void add_edge(int from, int to) {
	edge[++edge_num].next = head[from];
	edge[edge_num].to = to;
	head[from] = edge_num;
}

int stack[Maxn], top, inl[Maxn], isr[Maxn];
void fl(int u, int id) {
	inl[u] = id;
	for(int i = head[u]; i != -1; i = edge[i].next) {
		int v = edge[i].to;
		if(!inl[v]) W[id] += W[v], V[id] += V[v], fl(v, id);
	}
}

void topsort() {
	for(int i = 1; i <= N; ++i) {
		if(!deg[i]) stack[++top] = i;
	}
	while(top) {
		int u = stack[top--];
		for(int i = head[u]; i != -1; i = edge[i].next) {
			int v = edge[i].to;
			--deg[v];
			if(!deg[v]) stack[++top] = v;
		}
	}
	for(int i = 1; i <= N; ++i) {
		if(deg[i] && !inl[i]) isr[i] = 1, fl(i, i);
		else if(!inl[i]) inl[i] = i;
	}
}

void rebuild() {
	memset(edge, 0, sizeof(edge));
	memset(head, -1, sizeof(head));
	edge_num = 0;
	for(int i = 1; i <= N; ++i) if(inl[D[i]] != inl[i]) add_edge(inl[D[i]], inl[i]);
	for(int i = 1; i <= N; ++i) if(isr[i]) add_edge(S, i); 
}

int f[Maxn][Maxm];

void dfs(int u) {
//	cout << endl << u << " ";
	for(int i = head[u]; i != -1; i = edge[i].next) {
		int v = edge[i].to;
		dfs(v);
	}
	for(int i = W[u]; i <= M; ++i) f[u][i] = V[u];
	for(int i = head[u]; i != -1; i = edge[i].next) {
		int v = edge[i].to;
		for(int j = M; j >= W[u] + 1; --j) {
			for(int k = 0; k <= j - W[u]; ++k) {
				f[u][j] = max(f[u][j], f[v][k] + f[u][j - k]);
			}
		}
	}
}

int main() {
	N = read(); M = read();
	memset(head, -1, sizeof(head));
	for(int i = 1; i <= N; ++i) W[i] = read();
	for(int i = 1; i <= N; ++i) V[i] = read();
	for(int i = 1; i <= N; ++i) {
		D[i] = read();
		add_edge(i, D[i]);
		++deg[D[i]];
	}
	topsort();
	rebuild();
	dfs(S);
	int ans = 0;
	for(int j = 0; j <= M; ++j) ans = max(ans, f[0][j]);
	cout << ans << endl;
	return 0; 
}

[HDU3853] LOOPS

一道比较基础的期望 dp 题目。

题意

有一个 \(R \times C\) 的迷宫,从 \((1,1)\) 走到 \((R,C)\),每个格子给出停留在原地,向右走一格和向下走一格的概率,且每走一步需要 \(2\) 点能量,求最后所需要的能量期望。

  • \(R,C \leq 1000\)

解题

显然这道题适合 逆推

\(f[i][j]\) 表示从 \((i,j)\) 走到 \((R,C)\) 的期望次数(\(2\) 放到最后来乘)。假设此处停留、向下、向右的概率分别为 \(sto_{i,j},dow_{i,j},lef_{i,j}\),显然可以列出方程如下:

\(f[i][j] = sto_{i,j} \times f[i][j] + dow_{i,j} \times f[i+1][j] + lef_{i,j} \times f[i][j+1] +1\)

移项即得 \(f[i][j] = \frac {dow_{i,j} \times f[i+1][j] + lef_{i,j} \times f[i][j+1] +1}{1-sto_{i,j}}\)

然后就可以开始愉快地 dp 了。另外不知道为什么这道题遇到 \(sto_{i,j}=1\) (也就是在此处死循环)的时候要直接 continue;,难道期望不就应该变成 \(\infty\) 吗。。。

程序

#include <iostream>
#include <cstring>
#include <cstdio>

#define Maxn 1010

using namespace std;

int read() {
	int x = 0, f = 1;
	char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1;
		c = getchar();
	}
	while('0' <= c && c <= '9') {
		x = x * 10 + c - '0';
		c = getchar();
	}
	return x * f;
}

int N, M;
double sto[Maxn][Maxn], dow[Maxn][Maxn], lef[Maxn][Maxn], f[Maxn][Maxn];

int main() {
	while(scanf("%d%d", &N, &M) != EOF) {
		memset(sto, 0, sizeof(sto));
		memset(dow, 0, sizeof(dow));
		memset(lef, 0, sizeof(lef));
		memset(f, 0, sizeof(f));
		for(int i = 1; i <= N; ++i) {
			for(int j = 1; j <= M; ++j) {
				scanf("%lf", &sto[i][j]);
				scanf("%lf", &lef[i][j]);
				scanf("%lf", &dow[i][j]);
			}
		}
		for(int i = N; i >= 1; --i) {
			for(int j = M; j >= 1; --j) {
				if(i == N && j == M) continue;
				if(sto[i][j] >= 0.999999) continue;
				f[i][j] = (f[i + 1][j] * dow[i][j] + f[i][j + 1] * lef[i][j] + 1) / (1 - sto[i][j]);
			}
		}
		printf("%.3lf\n", f[1][1] * 2);
	}
	return 0;
}
posted @ 2020-08-02 13:51  Sqrtyz  阅读(129)  评论(0)    收藏  举报