先挖坑,以后慢慢填。。。

文章目录

  • T1.生日礼物
  • T2.大理石
    • 题目描述
    • 题解
    • 参考代码
  • T3.猜数字
    • 题目描述
    • 题解
    • 参考代码
  • T4.加分二叉树
    • 题目描述
    • 题解
    • 参考代码
  • T5.数字计数
    • 题目描述
    • 题解
    • 参考代码
  • T6.逛公园
  • T7.征途
    • 题目描述
    • 题解
    • 参考代码
  • T8.股票交易
  • T9.麻将
  • T10.太空梯
    • 题目描述
    • 题解
    • 参考代码
  • 后记

T1.生日礼物

T2.大理石

题目描述

题目描述
(256MiB / 4000ms)
林老师是一位大理石收藏家,他在家里收藏了n块各种颜色的大理石,第i块大理石的颜色为ai。但是林老师觉得这些石头在家里随意摆放太过凌乱,他希望把所有颜色相同的石头放在一起。换句话说,林老师需要对现有的大理石重新进行排列,在重新排列之后,对于每一个颜色j,如果最左边的颜色为j的大理石是第l块大理石,最右边的颜色为j的大理石是第r块大理石,那么从第l块大理石到第r块大理石,这些石头的颜色都为j。
由于这些大理石都比较重,林老师无法承受这些大理石的重量太久,所以他每次搬运只能交换相邻的两块大理石。请问,林老师最少需要进行多少次搬运?

输入格式
第一行输入一个数字n(2≤n≤4*10^5),表示大理石的总数。
第二行输入n个数字a1,a2…,an(1≤ ai ≤20)表示第i块大理石的颜色为ai

输出格式
输出林老师最少需要搬运的次数。

样例输入输出
Sample Input 1
7
3 4 2 3 4 2 2
Sample Output 1
3

Sample Input 2
5
20 1 14 10 2
Sample Output 2
0

Sample Input 3
13
5 5 4 4 3 5 7 6 5 4 4 6 5
Sample Output 3
21
加粗样式

题解

首先看到这道题时,第一反应就是搜索,但当我们看到数据范围时,就傻眼了2≤n≤4*10^5,与之对比很明显的时颜色的范围1≤ ai ≤20,只有20种颜色,于是,我们可以非常容易地想到用状压DP(此处不再赘述)

有了这个思路,我们可以很容易地想到用二进制来表示当且成堆的大理石的状态,用20位数字表示20种颜色是否全部挪到位了,全部到位则为1,否则为0.复杂度为2^20 = 1048576 ≈ 1e6,显然能过

接下来,我们寻找它的状态转移方程. 与很多状态转移方程类似,

dp[i+(1<<j)]=min(dp[i+(1<<j)],dp[i]+cost),dp[0]=0dp[i + (1 << j)] = min ( dp[i + (1 << j)], dp[i] + cost ),dp[0] = 0dp[i+(1<<j)]=min(dp[i+(1<<j)],dp[i]+cost),dp[0]=0


剩下的工作就只剩计算cost
根据题目描述可以知道,大理石只能交换相邻的两个,即,只能与 i + 1 或 i - 1 交换,稍微思考一下就可以发现,i 与 i + 1 交换即是 i + 1 与 (i + 1) - 1交换,所以,我们只用讨论一个方向即可(接下来以往左移为例

由于只能与相邻的大理石进行交换,所以,除了交换的两个大理石,其他大理石的相对位置是没有发生改变的,那么,我们就可以预处理出每两种颜色交换所需要的移动次数,并用一个cost[][]数组存下来,以便后来的dp计算直接调用

具体来说,cost[i][j]表示把所有颜色为j的大理石挪到所有颜色为i的大理石之前所需要的花费,即统计对于所有颜色为j的大理石而言,在它之前的颜色为i的大理石的个数,只需要双重循环累加即可

最后,枚举每一种情况并计算其花费就可以了

参考代码

#include <cstdio>
#include <iostream>
using namespace std;
#define INf 1ll << 60     //注意一下范围,(1 << 31) - 1过不了
#define LL long longconst int N = 20;
const int M = 4 * 1e5;
int n, maxx, a[M + 5], cnt[N + 5];
LL dp[(1 << N) + 5], cost[N + 5][N + 5];int main () {scanf ("%d", &n);for (int i = 1; i <= n; ++ i) {scanf ("%d", &a[i]);maxx = max (maxx, a[i]);  //maxx不能取成a[i] - 1-- a[i];}for (int i = 1; i <= n; ++ i) {++ cnt[a[i]];for (int j = 0; j < maxx; ++ j)cost[j][a[i]] += cnt[j]; }dp[0] = 0;for (int i = 1; i < (1 << maxx); ++ i)dp[i] = INf;for (int i = 0; i < (1 << maxx); ++ i) {for (int j = 0; j < maxx; ++ j) {if ((i & (1 << j))) continue;LL cost_ = 0;for (int k = 0; k < maxx; ++ k)if (i & (1 << k))cost_ += cost[j][k];dp[i + (1 << j)] = min (dp[i + (1 << j)], dp[i] + cost_);}}printf ("%lld\n", dp[(1 << maxx) - 1]);return 0;
}

T3.猜数字

题目描述

题目描述
( 256 MiB / 2000 ms )
g老师作为**中学最著名的猜测大师,他对几乎所有的事情都可谓料事如神。这不,g老师又将大展身手,准备进行新一轮事件的猜测。现在,g老师手里有n个数字,他需要猜出一个数字,使得这n个数字都是这个数字除1和它本身外的所有的其他因数。g老师觉得这种问题过于简单,不屑于回答,于是就把这个问题丢给了你们。

输入格式
第一行输入一个数字t(1≤t≤25),表示需要猜测的数字个数。
对于每次猜测,第一行输入一个数字n(1≤n≤300),表示此次猜测的因数个数。
第二行输入n个数字a1,a2,…,an(2≤ai≤10^6),表示需要猜测的数字的第i个因数。题目保证输入的ai各不相同。

输出格式
输出一个数字,即郭老师需要猜测的这个数。如果没有满足题目条件的数字,则输出-1.

样例输入输出
Sample Input
2
8
8 2 12 6 4 24 16 3
1
2

Sample Output
48
4

题解

由于出题人(或者我??)语言功底不佳,所以我搞了半天才搞懂题意,就是水题一道

由于题目保证了a[]是答案除本身和1以外的所有因数,且各不相同,再加上我们知道所有数的因数都是成对出现的,所以直接排序首位乘尾位即可

在计算的过程中,我们可以判断一种输出-1的情况,即是有一对因数的乘积与其他的不同;第二种情况要再计算完之后才可以判断,即是题目给定的a[]数组没有包含完所有的因数,除上述两种情况外,都是合法的

参考代码

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define LL long longconst int N = 300;
int T, n, a[N + 5];int main () {scanf ("%d", &T);while (T --) {scanf ("%d", &n);for (int i = 1; i <= n; ++ i)   scanf ("%d", &a[i]);sort (a + 1, a + n + 1);LL res = 1ll * a[1] * a[n];bool flag = true;for (int i = 2; i < n; ++ i)if (res != 1ll * a[i] * a[n - i + 1]) {flag = false;break;}if (! flag) {printf ("-1\n");continue;}int ip = 1;for (LL i = 2; i * i <= res; ++ i) {if (res % i == 0) {if (a[ip] != i || a[n - ip + 1] != res / i) {flag = false;break;}++ ip;}}if (! flag)printf ("-1\n");elseprintf ("%lld\n", res);}return 0;
}

T4.加分二叉树

题目描述

题目描述

原题来自:NOIP 2003 (512MiB / 1000ms)

设一个n个节点的二叉树tree的中序遍历为(1,2,3,…,n),其中数字1,2,3,…,n为节点编号。每个节点都有一个分数(均为正整数),记第j个节点的分数为di,tree及它的每个子树都有一个加分,任一棵子树subtree(也包含tree本身)的加分计算方法如下:
subtree的左子树的加分× subtree的右子树的加分+subtree的根的分数
若某个子树为主,规定其加分为1,叶子的加分就是叶节点本身的分数。不考虑它的空子树。
试求一棵符合中序遍历为(1,2,3,…,n)且加分最高的二叉树tree。要求输出;
(1)tree的最高加分
(2)tree的前序遍历
现在,请你帮助你的好朋友XZ设计一个程序,求得正确的答案。

输入格式
第1行:一个整数n,为节点个数。
第2行:n个用空格隔开的整数,为每个节点的分数

输出格式
第1行:一个整数,为最高加分。
第2行:n个用空格隔开的整数,为该树的前序遍历。

数据范围及提示
对于100%的数据,n<30,b<100,结果不超过 4 * 10^9

样例输入输出
Sample Input
5
5 7 1 2 10

Sample Output
145
3 1 2 4 5

题解

前言
首先普及一下二叉树的先序、中序、后序遍历
先序即先根,顾名思义,其遍历顺序为 根–>左儿子–>右儿子
同理,
中序即左儿子–>根–>右儿子
后序即左儿子–>右儿子–>根


正文
下面开始进行该题的讲解

题目给出一颗二叉树的中序遍历,要求前序遍历的最大加分,很显然,我们要做的工作就是,对中序遍历的这棵树进行划分,找出哪些节点是某一棵子树的根,哪些节点是这棵子树的左儿子,哪些节点是这棵子树的右儿子时, 可以得到该子树的最大加分

于是,将题目转化为一道区间dp的板题

根据题目给出的公式(tree = l * r + a),我们设dp[i][j]表示节点i到节点j的最大加分,root[i][j]表示该子树的根节点的编号,于是我们可以得出递推式
dp[i][j]=max(dp[i][k−1]∗dp[i][k+1]+val[k]),k为枚举的根节点dp[i][j] = max (dp[i][k - 1] * dp[i][k + 1] + val[k]), k为枚举的根节点dp[i][j]=max(dp[i][k−1]∗dp[i][k+1]+val[k]),k为枚举的根节点

答案输出 dp[1][n] 即可

参考代码

(本题解法很多,下面提供一种比较简单的递归算法)

#include <cstdio>
#include <iostream>
using namespace std;const int N = 30;
int n, val[N + 5];
int dp[N + 5][N + 5], root[N + 5][N + 5];int dfs (const int l, const int r) {if (dp[l][r])return dp[l][r];if (l == r) {dp[l][r] = val[l];root[l][r] = l;return dp[l][r];}if (l > r)return 1;for (int i = l; i <= r; ++ i) {int t = dfs (l, i - 1) * dfs (i + 1, r) + val[i];if (dp[l][r] < t) {dp[l][r] = t;root[l][r] = i;}}return dp[l][r];
}void print (const int l, const int r) {printf ("%d ", root[l][r]);if (root[l][r] > l)print (l, root[l][r] - 1);if (root[l][r] < r)print (root[l][r] + 1, r);
}int main () {scanf ("%d", &n);for (int i = 1; i <= n; ++ i)scanf ("%d", &val[i]);dfs (1, n);printf ("%d\n", dp[1][n]);print (1, n);return 0;
}

T5.数字计数

题目描述

题目描述

原题来自:ZJOI 2010 (512MiB / 1000ms)
给定两个正整数a和b,求在[a, b]中的所有整数中,每个数码 (digit) 各出现了多少次。

输入格式
仅包含一行两个整数 a, b,含义如上所述。

输出格式
包含一行10个整数,分别表示0 ~ 9在[a, b]中出现了多少次。

数据范围及提示
30%的数据中,1 ≤ a ≤ b ≤ 10^6,
100%的数据中,1 ≤ a ≤ b ≤ 10^12

样例输入输出
Sample Input
1 99

Sample Output
9 20 20 20 20 20 20 20 20 20

题解

首先,对于30%的数据而言,可以直接暴力枚举每一个数字,然后分离数位计算结果
但是,对于后70%的数据而言,暴力显然是无能为力的,首先10^12就把我吓傻了

冷静下来思考,对于每一道形如求区间[a, b]的某个值的题目,我们都可以很轻松地求地 区间[1, x]或区间[0, x]的值,这个规律在本题也适用,因此,我们可以把区间[a, b]转换成区间[1, b] - 区间[1, a - 1]

显而易见,这道题不是用dp就是用搜索(当然是记忆化),但由于我是个蒟蒻,所以没写出dp,但记忆化确实挺简单的

对于数字i,我们定义dp[len][j][k]表示在长度为len的数字中,i出现j次的情况(其中k表示是否有前导零的)i的个数(有点绕,希望我说清楚了),那么,长度为len的情况只与长度为len - 1的情况有关,因此我们可以采用递归的形式,用记忆化进行优化.

(语文功底很有限啊,口胡只能这样了,代码挺通俗的,慢慢琢磨吧。。。)

对于我上面提到的数位dp的算法,我给出一篇代码通俗易懂的博客,博主文风清奇,忍住别笑

参考代码

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
#define LL long longconst int N = 12;
LL cur, t[N + 5], dp[N + 5][N + 5][2];LL dfs (const int len, const int times, const bool preZero, const bool limit) {//len表示数字的长度, times表示数字cur出现的次数, preZero表示是否包含前导零, limit表示对首位是否有限制if (len == 0)return 1ll * times;if (! limit && dp[len][times][preZero] != -1)return dp[len][times][preZero];int up = limit ? t[len] : 9;LL ans = 0;for (int i = 0; i <= up; ++ i) {ans += dfs (len - 1, times + ((i || preZero) && (i == cur)), i || preZero, limit && i == t[len]);}if (! limit)dp[len][times][preZero] = ans;return ans;
}LL sovle (LL x) {int len = 0;while (x) {t[++ len] = x % 10;x /= 10;}return dfs (len, 0, 0, 1);
}int main () {LL n, m;scanf ("%lld %lld", &n, &m);for ( ; cur < 10; ++ cur) {memset (dp, -1, sizeof (dp));LL ans = sovle (m) - sovle (n - 1);printf ("%lld ", ans);}return 0;
}

T6.逛公园

T7.征途

题目描述

题目描述
Pine 开始了从S地到T地的征途。
从S地到T地的路可以划分成n段,相邻两段路的分界点设有休息站。
Pine 计划用m天到达T地。除第m天外,每一天晚上 Pine 都必须在休息站过夜。所以,一段路必须在同一天中走完。
Pine 希望每一天走的路长度尽可能相近,所以他希望每一天走的路的长度的方差尽可能小。
帮助 Pine 求出最小方差是多少。
设方差是v,可以证明,v * m²是一个整数。为了避免精度误差,输出结果时输出v * m²。

输入格式
第一行两个数nm。
第二行n个数,表示n段路的长度。

输出格式
一个数,最小方差乘以m²后的值。

样例输入输出
Sample Input
5 2
1 2 5 8 6

Sample Output
36

题解

前言
题目中提到了方差的概念,给大家普及一下:欢迎给度娘增加访问量 :)

方差是实际值与期望值之差平方的平均值
方差是各个数据与平均数之差的平方的和的平均数, 即:
s²=1n[(x1−x)2+(x2−x)2+...+(xn−x)2]s² = \frac{1}{n}[(x_1 - x)^2 + (x_2 - x) ^ 2 + ... + (x_n - x) ^2]s²=n1​[(x1​−x)2+(x2​−x)2+...+(xn​−x)2]
其中,x表示样本的平均数,n表示样本的数量,xi表示个体,而s2就表示方差。


正文
看到这道题,我们首先要知道我们要求什么。根据题意,我们最后需要输出的是v * m²,其中v是方差

显然,这道题是要用dp的。那么,我们设第i天的路程为ai,那么v=∑(ai−(Sm)2mv= \frac{∑(a_i - (\frac{S}{m})^2}{m}v=m∑(ai​−(mS​)2​,代入所求式子中,化简可得: ans=m∗∑ai2−S2ans = m * ∑a_i^2-S^2ans=m∗∑ai2​−S2

进一步思考,由于m和s都是已知的,所以只需让∑ai²∑a_i²∑ai​²最小即可。
要使得和最小,我们可以设dp[i][j]表示前i天走了前j段路的最优解(前i天所走路程的平方和),那么很容易可以想到状态转移方程式:
dp[i][j]=min(dp[i−1][k]+(pre[j]−pre[k])2)dp[i][j] = min (dp[i - 1][k] + (pre[j] - pre[k])^2)dp[i][j]=min(dp[i−1][k]+(pre[j]−pre[k])2),其中pre[i]表示前i段路的长度和,1 ≤ k<j。利用这个状态转移方程,我们可以很容易地对答案进行dp求解,时间复杂度为O(m3)O(m^3)O(m3),对于100%的数据,显然是拿不全的


接下来,我们开始思考优化,对于这个式子,它的第一维(即i)是可以滚动的,因此,我们考虑斜率优化
对于节点i,我们考虑a和b两种情况。若a比b优,那么有:
dp[j−1][a]+(prei−prea)2<dp[j−1][b]+(prei−preb)2dp[j - 1][a] + (pre_i - pre_a) ^ 2 < dp[j - 1][b] + (pre_i - pre_b) ^ 2dp[j−1][a]+(prei​−prea​)2<dp[j−1][b]+(prei​−preb​)2
用平方差公式展开,化简得:
dp[j−1][a]+prea²−2∗prei∗prea<dp[j−1][b]+preb²−2∗prei∗prebdp[j - 1][a] + pre_a² - 2 * pre_i * pre_ a < dp[j - 1][b] + pre_b² - 2 * pre_i * pre _ bdp[j−1][a]+prea​²−2∗prei​∗prea​<dp[j−1][b]+preb​²−2∗prei​∗preb​
进一步变形,得:
(dp[j−1][a]+presa²)−(dp[j−1][b]+preb²)prea−preb<2∗prei\frac{(dp[j - 1][a] + pres_a²) - (dp[j - 1][b] + pre_b²)}{pre_a - pre_b} < 2 * pre_iprea​−preb​(dp[j−1][a]+presa​²)−(dp[j−1][b]+preb​²)​<2∗prei​
化简到此,我们可以发现,不等式左边其实就是一个斜率的式子,然后直接套上斜率优化的模板就可以了

参考代码

(未加斜率优化,60分暴力dp)

#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
using namespace std;const int N = 3000;
int n, m, a[N + 5], pre[N + 5];
int dp[N + 5][N + 5];int main () {scanf ("%d %d", &n, &m);for (int i = 1; i <= n; ++ i) {scanf ("%d", &a[i]);pre[i] = pre[i - 1] + a[i];}memset (dp, 0x3f, sizeof (dp));dp[0][0] = 0;for (int i = 1; i <= m; ++ i)for (int j = 1; j <= n; ++ j)for (int k = 0; k < j; ++ k)dp[i][j] = min ( dp[i][j], dp[i - 1][k] + (pre[j] - pre[k]) * (pre[j] - pre[k]) );printf ("%lld\n", m * dp[m][n] - pre[n] * pre[n]);return 0;
}

(加上斜率优化后开心地Ac了

[2019年国庆专题训练] dp专题训练相关推荐

  1. 训练dnn_[预训练语言模型专题] MT-DNN(KD) : 预训练、多任务、知识蒸馏的结合

    本文为预训练语言模型专题系列第八篇 快速传送门 1-4:[萌芽时代].[风起云涌].[文本分类通用技巧].[GPT家族] 5-7:[BERT来临].[浅析BERT代码].[ERNIE合集] 感谢清华大 ...

  2. 训练dnn_[预训练语言模型专题] MTDNN(KD) : 预训练、多任务、知识蒸馏的结合

    本文为预训练语言模型专题系列第八篇 快速传送门  1-4:[萌芽时代].[风起云涌].[文本分类通用技巧] . [GPT家族]5-7:[BERT来临].[浅析BERT代码].[ERNIE合集]感谢清华 ...

  3. [预训练语言模型专题] MT-DNN(KD) : 预训练、多任务、知识蒸馏的结合

    本文为预训练语言模型专题系列第八篇 快速传送门   1-4:[萌芽时代].[风起云涌].[文本分类通用技巧] . [GPT家族] 5-7:[BERT来临].[浅析BERT代码].[ERNIE合集] 感 ...

  4. [预训练语言模型专题] 银色独角兽GPT家族

    本文为预训练语言模型专题系列第四篇 前期回顾:[萌芽时代].[风起云涌].[文本分类通用训练技巧] 感谢清华大学自然语言处理实验室对预训练语言模型架构的梳理,我们将沿此脉络前行,探索预训练语言模型的前 ...

  5. DP专题考试总结(4)

    最近努力学(tui)习(fei)了DP专题,然后考试又挂了,然后就没有然后了. 对此,我只想说-- 吾每念,常痛于骨髓,顾计不知所出耳! 广场铺砖问题 期望得分:40 实际得分:10 Descript ...

  6. 用计算机语法表示谁在说谎,2019考研管理类联考逻辑思维训练题:假设法(2)

    现在已是4月底了,基础复习已紧张进行中,中公考研小编整理分享整理了一篇关于"2019考研管理类联考逻辑思维训练题:假设法(2)"文章,供2019备考的同学们参考. 第一章 假设法 ...

  7. DP专题考试总结(2)

    最近努力学(tui)习(fei)了DP专题,然后就被烤焦了,然后就没有然后了. 对此,我只想说-- DP made me Boom-Sha-Ka-La-Ka 加分二叉树 期望得分:0 实际得分:0 D ...

  8. DP地狱训练 挤牛奶

    1257: [DP地狱训练]挤牛奶 时间限制: 1 Sec  内存限制: 64 MB 提交: 917  解决: 260 [提交][状态][讨论版] 题目描述 小卡卡终于帮农夫John找到了走丢的那一头 ...

  9. 易智瑞(ESRI)2019最新ENVI5.5遥感应用专题操作录屏及讲义分享

    易智瑞(ESRI)2019最新ENVI5.5遥感应用专题操作录屏及讲义分享 资料来源: 一.内容目录 1.1 ENVI基础 001-ENVI入门与遥感图像处理基础 1.2 卫星传感器处理 101-处理 ...

  10. 浙大九推计算机学院,2019浙大经管类九推专题精讲课(内含真题)

    原标题:2019浙大经管类九推专题精讲课(内含真题) 经历了激烈残酷的夏令营大战,你是否杀出重围拿到了自己满意的offer? 还是offer在手,但却不是自己心之所向? 或是不幸败北,"颗粒 ...

最新文章

  1. 计算机设置内存储器的必要性,计算机设置内存储器的必要性
  2. u盘文件看得见却打不开_U盘中病毒,文件看得见,但是打不开,文件要怎么修复,求解答?...
  3. MyBatis 缓存详解-缓存体系结构
  4. JAVA内存存储数据的位置
  5. SQL多列查询最大值
  6. 超声和免疫学指标的特征能否反映RA临床缓解的表型?[EULAR2015_THU0121]
  7. 从富文本中截取图片_JS 获取富文本中的第一张图片 (正则表达式)
  8. 安全运维 - Windows系统应急响应
  9. 视频分析服务器作用,如何选择合适的视频分析解决方案?
  10. GitHub预测2018年开源项目趋势
  11. java基础-软件简述
  12. 手把手一步步用DataGridView 控件编写属于自己的日历
  13. iOS gzip解压
  14. 什么人适合学平面设计?
  15. 亦师亦友——忆我在北邮四年中的几位老师(全)
  16. 前后端齐全的扫码点餐小程序(后端Java)
  17. 二叉树、BTree、B+Tree
  18. 华为进军区块链,区块链最好的时代来了吗?
  19. Swagger2.0
  20. 提示 you neet to root to perform this command 的解决办法

热门文章

  1. Excel表格合并两列数据且保留全部内容
  2. SAR图像的相干斑噪声
  3. mysql计算增长率
  4. matlab函数grid,Matlab基本函数-grid、box函数
  5. Excel表格快捷键技巧使用
  6. 【python 走进NLP】hanNLP 简繁拼音转换
  7. php c端,tob端和toc端是什么意思
  8. ubuntu上打开md文件_Ubuntu 使用教程.md
  9. 夜神模拟器安装fiddler证书
  10. php 依赖倒置原则,PHP:依赖注入,控制反转,依赖倒置原则