前言:
动态规划经常出现,但自己在这方面稍显薄弱,就想记录自己平时做过的简单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;
}