Codeforces 598D Igor In the Museum(概率DP)

@(K ACMer)


题意:
在x轴上有n颗高度为h的树,现在你将有 12 的概率,砍最左边的树,同样的概率砍最右边的树.然后对于每个树它有 p 的概率会向左边倒,(1p)的概率会向右边倒.值得注意的是树的倒会引起类似多米诺骨牌效应,也就是如果一个树和它相邻的树和它的距离小于h,它往这颗树倒的时候,该树也会向同样的方向倒.为你这些树全部倒下的期望覆盖地面长度是多少?


分析:
开始很很容易想到一个记忆化搜索的思路,定义 dfs(l,r,lf,rf) 为当前的最左边界的下标为 l 最右边界下标为r,最靠近 l 的覆盖到了lf,最靠近 r 的覆盖到了rf.这样再分为:1.左边的树向左边倒,2.左边的树向右边倒,3.右边的树向左边倒,4.右边的树向右边倒四种情况来转移就可.
但是写出来记忆优化的时候发现 lf rf 都太大了,必须需要用map来离散化,所以记忆化的数组是 map<pair<int,int>,double>dp[l][r] 这样凭空多了个 O(logn) 的复杂度,既超时间也超内存.
最后发现其实没有必要记录 lf ,和 rf 这样大的数只需要记录当前 l 左边的树是向左倒还是向右倒,当前r右边的树是向左倒还是向右倒,就可以推算出来 lf rf .这是有状态 dp[l][r][2][2]


code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
#include <map>
#include <stack>
#include <vector>
#include <string>
#include <queue>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
typedef pair<int, int> pii;
typedef long long ull;
typedef long long ll;
typedef vector<int> vi;
#define xx first
#define yy second
#define rep(i, a, n) for (int i = a; i < n; i++)
#define sa(n) scanf("%d", &(n))
#define vep(c) for(decltype((c).begin()) it = (c).begin(); it != (c).end(); it++)
const int mod = int(1e4) + 7, INF = 0x3fffffff, maxn = 1e5 + 12;
int n, h, v[2009];
double p, eps = 1e-11;
double dp[2009][2009][2][2];

double dfs(int l, int r, int lf, int rf)
{
    if (l > r) return 0;
    int li = l, ri =r;
    if (dp[li][ri][lf][rf]) return dp[li][ri][lf][rf];
    int lff = l > 0 ? (lf == 0 ? v[l - 1] : v[l - 1] + h ) : -INF;
    int rff = r < n - 1 ? (rf == 1 ? v[r + 1] : v[r + 1] - h) : INF;
    double ret = 0.0;
    ret += p * 0.5 * (min(h, v[l] - lff) + dfs(l + 1, r, 0, rf));
    int x = l + 1;
    while (x <= r && v[x] < v[x - 1] + h) x++;
    x--;
    ret += (1 - p) * 0.5 * (min(v[x] - v[l] + h, rff - v[l]) + dfs(x + 1, r, 1, rf));
    ret += (1 - p) * 0.5  * (min(h, rff - v[r]) + dfs(l, r - 1, lf, 1));
    x = r - 1;
    while (x >= l && v[x + 1] < v[x] + h) x--;
    x++;
    ret += p * 0.5 * (min(v[r] - v[x] + h, v[r] - lff) + dfs(l, x - 1, lf, 0));
    return dp[li][ri][lf][rf] = ret + eps;
    //return dp[li][ri][make_pair(lf, rf)] = ret + eps;
}

int main(void)
{
    sa(n), sa(h);
    cin >> p;
    rep (i, 0, n) sa(v[i]);
    sort(v, v + n);
    printf("%.15f\n", dfs(0, n - 1, 0, 0));
    return 0;
}
区间DP是一种动态规划的方法,用于解决区间范围内的问题。在Codeforces竞赛中,区间DP经常被用于解决一些复杂的字符串或序列相关的问题。 在区间DP中,dp[i][j]表示第一个序列前i个元素和第二个序列前j个元素的最优解。具体的转移方程会根据具体的问题而变化,但是通常会涉及到比较两个序列的元素是否相等,然后根据不同的情况进行状态转移。 对于区间长度为1的情况,可以先进行初始化,然后再通过枚举区间长度和区间左端点,计算出dp[i][j]的值。 以下是一个示例代码,展示了如何使用区间DP来解决一个字符串匹配的问题: #include <cstdio> #include <cstring> #include <string> #include <iostream> #include <algorithm> using namespace std; const int maxn=510; const int inf=0x3f3f3f3f; int n,dp[maxn][maxn]; char s[maxn]; int main() { scanf("%d", &n); scanf("%s", s + 1); for(int i = 1; i <= n; i++) dp[i][i] = 1; for(int i = 1; i <= n; i++) { if(s[i] == s[i - 1]) dp[i][i - 1] = 1; else dp[i][i - 1] = 2; } for(int len = 3; len <= n; len++) { int r; for(int l = 1; l + len - 1 <= n; l++) { r = l + len - 1; dp[l][r] = inf; if(s[l] == s[r]) dp[l][r] = min(dp[l + 1][r], dp[l][r - 1]); else { for(int k = l; k <= r; k++) { dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r]); } } } } printf("%d\n", dp[n]); return 0; } 希望这个例子能帮助你理解区间DP的基本思想和应用方法。如果你还有其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值