2022GDUT寒训专题二
B题 最长公共子序列
题面
样例
思路
首先已知所求的是两个排列的最长公共子序列,如果二者有公共子序列,那么这个子序列的各个元素在P1出现的顺序一定和在P2出现的顺序是一样的。
我们实际上可以做的是以P1为基准,将P2中的元素与P1中的元素进行匹配。也就是说现在我们假设将P1数组存在一个离散化的数组中,数组的下标为1 2 3 4 5 6......然后我们现在做的其实是将找P2数组中的数存在与P1的哪个位置,就这样我们可以得到一个新的数组,这个数组是P2中的元素在P1中的排列顺序中对应的下标。
前面说了这个公共子序列出现的顺序一样,我们现在假设的是P1的下标为1 2 3 4 5 6......为升序,那么刚刚得到新的数组的元素也应该是升序。
由此问题就可以转化为新数组中的最长上升子序列问题
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
//这题是求两个排列的最长公共子序列。
//因为排列的特殊性,我们可以将问题转换为以P1顺序标号的P2的排序数组的最长上升子序列问题。
int _index[N];
int x[N];
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(NULL);cout.tie(NULL);
//
int n;cin >> n;
for(int i = 1;i <= n;++i)
{
int temp;
cin >> temp;
//这个地方直接交换i和temp就行,因为i本身就是有序的。
_index[temp] = i;
}
//将数组设为无穷大方便lower_bound操作
//也就是免去了判断num是否大于最大值的操作
memset(x, 0x3f3f3f, sizeof(x));
for(int i = 1;i <= n;++i)
{
int temp;cin >> temp;
//直接得到P2的下标
int num = _index[temp];
int pos = lower_bound(x+1, x+1+n, num)-x;
x[pos] = num;
}
//
int ans = 0;
//如果走到了正无穷处,那他的前一位就是答案了
for(int i = 1;i <= n+1;++i) if(x[i] > n) {ans = i-1;break;}
cout << ans << endl;
return 0;
}
E题 CD
题面
题意
你有n个音乐片段和N个长度的时间,每段音乐都要播放一定长度的时间,现在问你如何刻录这n个音乐使得在不超过N的时限内刻录的音乐总时长最长。
样例
思路
这题一眼就知道是01背包,但是主要是要得到路径,那么我们可以用01背包的二维dp形式,因为我们需要得到每一层所存储的值,根据状态转移方程逆推即可得到所选取的CD
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 30;
//这题就是01背包
int num[maxn];
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(NULL);cout.tie(NULL);
//
int N, n;
while(cin >> N >> n)
{
for(int i = 1;i <= n;++i) cin >> num[i];
vector<vector<int> >dp(N+1, vector<int>(N+1, 0));
//
for(int i = 1;i <= n;++i)
{
for(int j = 1;j <= N;++j)
{
dp[i][j] = dp[i-1][j];
if(j >= num[i]) dp[i][j] = max(dp[i-1][j], dp[i-1][j-num[i]]+num[i]);
}
}
//由上面的式子我们可以知道,如果选中了num[i]
//那么恒有dp[i][t] - dp[i-1][t-num[i]] == num[i],其中t为当前体积
//所以可以反推回原序列
int t = dp[n][N];
for(int i = n;i >= 1;--i)
{
if(dp[i][t] - dp[i-1][t-num[i]] == num[i])
{
cout << num[i] << " ";
t -= num[i];
}
}
cout << "sum:" << dp[n][N] << endl;
}
return 0;
}
G题 Flipping coins
题面
题意
原先有n枚硬币反面朝上,现在你必须投掷K次硬币,每次投掷硬币可能会使得硬币变成正面或者反面,现在你想要让正面朝上的硬币数量最多,请输出投了K次硬币之后硬币正面朝上个数的期望。
思路
因为是想要正面朝上的硬币数量最多,所以在1~n-1范围内只可能是去投掷反面的硬币,但是因为必须投掷k次,所以就要对n-1特判
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int, int> PII;
const int maxn = 410;
//dp数组实际上表示的是投掷了j次使得i枚硬币朝上的概率
double dp[maxn][maxn];
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(NULL);cout.tie(NULL);
//
int n, k;cin >> n >> k;
//
dp[0][0] = 1.0;
//这个地方一定要让i在内层,因为这个是必须先进行了上一轮硬币的投掷才可以进行下一轮的,不然答案会错
for(int j = 1;j <= k;++j)
{
for(int i = 0;i <= n;++i)
{
//一定会想要先投反面的硬币,因为想要正面朝上的硬币最多
//但是如果达到了n个硬币朝上,那不得不选取一枚正面朝上的硬币来投掷了,所以对n-1特判
if(i > 0) dp[i][j] = (0.5*dp[i][j-1] + 0.5*dp[i-1][j-1]);
else dp[i][j] = 0.5*dp[i][j-1];
if(i == n-1) dp[i][j] += 0.5*dp[n][j-1];
}
}
//最后让硬币个数乘上概率就是总期望
double ans = 0.0;
for(int i = 1;i <= n;++i)
{
ans += dp[i][k]*i;
}
printf("%.6f\n", ans);
return 0;
}
H题 Discovering Gold
题面
样例
题意
现在有一个1xN的山洞,每一格内有一定数量的金子,你通过投骰子(骰子大小为1~6)来决定前进几格。特别的:不可以走出山洞(即N的范围),若出现这样的情况将重新投骰子。现在让你求走到山洞的底部时所得到金子的期望。
思路
这题总体的思路很简单,实际上就是简单的递推。主要需要注意的是这次的状态含义不同。
dp状态含义是离终点还有i格的总期望。
解释如下:
我们需要理解一件事情:事件A在事件B的条件下发生的概率和事件B在事件A的条件下发生的概率是不一样的。
所以如果你是正向来算这个期望的话,算的期望实际上是大于你想要求得走到N点的期望的,可以在纸上假设只有四个格子的情景,手推看看就能发现了。
而对于走到N点这个状态是我们想要求的一个最终的状态,是确定的状态,所以要从确定态出发。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int, int> PII;
const int maxn = 300;
double dp[maxn];
int main()
{
// ios_base::sync_with_stdio(false);
// cin.tie(NULL);cout.tie(NULL);
//
int t, n;scanf("%d", &t);
int cases = 0;
while(t--)
{
scanf("%d", &n);
memset(dp, 0, sizeof(dp));
for(int i = 1;i <= n;++i) scanf("%lf", &dp[i]);
for(int i = n-1;i >= 1;--i)
{
for(int j = 1;j <= 6;++j)
{
dp[i] += dp[i+j]/(1.0*min(n-i, 6));
}
}
printf("Case %d: %.7f\n",++cases,dp[1]);
}
return 0;
}
总结
DP专题要整理的题真是太多了(因为都不会啊)不过好在专题还是跌跌撞撞地做得差不多了,见识过各种各样奇妙的dp,学习别人分析DP的思路,还是慢慢能做出来一些的吧qvq。
大概现在遇到的分类是线性/区间/概率/期望/计数吧。