2022GDUT寒训专题二

B题 最长公共子序列

题面

image

样例

image

思路

首先已知所求的是两个排列的最长公共子序列,如果二者有公共子序列,那么这个子序列的各个元素在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

题面

image

题意

你有n个音乐片段和N个长度的时间,每段音乐都要播放一定长度的时间,现在问你如何刻录这n个音乐使得在不超过N的时限内刻录的音乐总时长最长。

样例

image

思路

这题一眼就知道是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

题面

image

题意

原先有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

题面

image

样例

image

题意

现在有一个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。
大概现在遇到的分类是线性/区间/概率/期望/计数吧。

posted @ 2022-01-25 23:03  _77  阅读(40)  评论(0编辑  收藏  举报