简单动态规划做题记录

前言:

动态规划经常出现,但自己在这方面稍显薄弱,就想记录自己平时做过的简单dp题,也方便往后复习总结【这是不可能的】

(1)2018CCPC吉林:The Moon【期望dp】

分析:

当时现场赛看到这题人都傻了,那时都还没听说过期望dp

这题找到一个已知状态就好做了;发现当q变成100%时就与q的概率无关了,只和p的概率有关,而p的概率固定,所以此时期望为1/p;初始时q = 2%,定义dp[x]为q的概率为x时得到物品的期望局数,将分数同时乘上200,倒推q就能写了;每局要么赢并且得不到(得到就结束了,期望贡献包含在1里),要么输了进入第四步,相应的概率乘上期望即可

代码:

#include <bits/stdc++.h>

using namespace std;
const int maxn = 205;
double dp[maxn];
int T,p;
double solve(){
	dp[200] = 100.0/p;
	for(int i = 199;i >= 4; --i){
		dp[i] = p/100.0*(1-i/200.0)*dp[min(200,i+4)]+(1-p/100.0)*dp[min(200,i+3)]+1;
	}
	return dp[4];
}
int main(){
	scanf("%d",&T);
	for(int Case = 1;Case <= T; ++Case){
		scanf("%d",&p);
		printf("Case %d: %.7f\n",Case,solve());
	}
	return 0;
}

(2)Comet OJ:符文能量【线性dp】

分析:

发现石子合并的顺序与最终答案无关就可以做了,答案只会是b[i-1]*a[i]之和,只需要考虑给那一段连续的区间乘上K;定义dp[i][0/1/2],dp[i][0]表示前i项都没有乘过K,dp[i][1]表示第i项乘上K,dp[i][2]表示从第i项开始不能再乘K了(包括第i项) 

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int maxn = 1e5+15;
int n,k,a[maxn],b[maxn];
LL dp[maxn][3];
int main(){
    cin >> n >> k;
    for(int i = 1;i <= n; ++i) cin >> a[i] >> b[i];
    for(int i = 2;i <= n; ++i){
        dp[i][0] = dp[i-1][0]+1LL*b[i-1]*a[i];
        dp[i][1] = min(dp[i-1][0]+1LL*b[i-1]*a[i]*k,dp[i-1][1]+1LL*b[i-1]*a[i]*k*k);
        dp[i][2] = min(dp[i-1][2]+1LL*b[i-1]*a[i],dp[i-1][1]+1LL*b[i-1]*a[i]*k);
    }
    cout << min(dp[n][0],min(dp[n][1],dp[n][2])) << '\n';
    return 0;
}

(3)CF1114D:Flood Fill【区间dp】

分析:

开始时,自信定义了dp[i][j]表示把前i个都涂成颜色j的最小次数,这样是有错的,题目说每次必须把一个联通块都涂上一个颜色,每次涂完了可能会改变联通块的状态,比如:12321,先3后2才是最优方案;题解颠覆了我一直以为区间dp都得N^3的概念,定义dp[L][R][0/1],dp[L][R][0]表示将【L,R】区间都涂成Color[L]的颜色的最小次数,dp[L][R][1]则是涂成Color[R],都说这种涂法才是最优的,大致意思是从中间开始涂,这样就可以不断的合并左右相同的块,最后不是左边就是右边

代码:

#include <bits/stdc++.h>
 
using namespace std;
const int maxn = 5e3+53;
int n,c[maxn],dp[maxn][maxn][2];
int main(){
    cin >> n;
    for(int i = 1;i <= n; ++i) cin >> c[i];
    for(int len = 2;len <= n; ++len){
        for(int L = 1;L <= n-len+1; ++L){
            int R = L + len - 1;
            dp[L][R][0] = min(dp[L+1][R][0]+(c[L]==c[L+1]?0:1),dp[L+1][R][1]+(c[L]==c[R]?0:1));
            dp[L][R][1] = min(dp[L][R-1][1]+(c[R]==c[R-1]?0:1),dp[L][R-1][0]+(c[L]==c[R]?0:1));
        }
    }
    cout << min(dp[1][n][0],dp[1][n][1]) << '\n';
    return 0;
}

(4)CF510D:Fox And Jumping【线性dp】

分析:

假设选了三个数A,B,C,从起点S到终点T时这三个数分别加了x,y,z次(可正可负),那么有S+Ax+By+Cz = T;无论出发点S为多少,都可以转换为从0出发,所以有Ax+By+Cz = T ,由贝祖定理:上述方程有解当且仅当T%gcd(A,B,C) = 0,而T又是连续的整数,得到:gcd(A,B,C) = 1,所以问题转化为选一些数,使得他们gcd = 1的最小花费是多少?定义mp[x],表示gcd=x时的最小花费,由于gcd会很大,所以用map记录已经存在的状态,每个数和其他数的gcd个数不会超过log个,这是时间的保障

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int maxn = 1e3+12;
map<int,int> mp[2];
int a[maxn],b[maxn];
int main(){
    int n; cin >> n;
    for(int i = 1;i <= n; ++i) cin >> a[i];
    for(int i = 1;i <= n; ++i) cin >> b[i];
    for(int i = 1;i <= n; ++i){
        int now = i&1,per = (i-1)&1;
        mp[now].clear();mp[per][0] = 0;
        for(map<int,int>::iterator it= mp[per].begin(); it!=mp[per].end(); ++it){
            if(!mp[now].count(it->first)) mp[now][it->first] = it->second;
            else mp[now][it->first] = min(mp[now][it->first],it->second);
            int gcd = __gcd(it->first,a[i]);
            if(!mp[now].count(gcd)) mp[now][gcd] = it->second+b[i];
            else mp[now][gcd] = min(mp[now][gcd],it->second+b[i]);
        }
    }
    if(!mp[n&1].count(1)) puts("-1");
    else cout << mp[n&1][1] << '\n';
    return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值