1. 数字游戏
来源:NOIP2003普及组 https://ac.nowcoder.com/acm/contest/231/B
算法知识点: DP,区间DP
复杂度: &preview=true)
解题思路:
首先将环从起点处断开,然后复制一遍接在后面,这样原问题就转化成了线段型的模型。
然后从集合角度来分析状态表示和状态计算:
状态表示:
f[l][r][i],表示所有将a[l...r]分割成i部分的方案的最大乘积;g[l][r][i],表示所有将a[l...r]分割成i部分的方案的最小乘积;
状态计算:
首先考虑f[l][r][i]如何计算,关键是寻找“集合划分的依据”,划分依据一般选取“最后一步的操作”,所以这里我们可以按最后一部分的位置来将f[l][r][i]所表示的所有方案划分成若干类:
- 最后一段是
a[l + i - 1...r]的所有划分方案; - 最后一段是
a[l + i...r]的所有划分方案; - ...
- 最后一段是
a[r...r]的所有划分方案;
如果最后一段是a[k...r],那么这一类的最大值是 f[l][k - 1][i - 1] * sum(a[k...r]),其中sum()计算某一段数的和模10的余数。
f[l][r][i]取所有类别的最大值即可。g[l][r][i]的计算方式类似。
最终枚举所有长度是n的区间,取最大值/最小值即可。
C++ 代码:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110, M = 10, INF = 1e9;
int n, m;
int a[N], f[N][N][M], g[N][N][M];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
{
cin >> a[i];
a[i + n] = a[i];
}
for (int i = 1; i <= n * 2; i ++ ) a[i] += a[i - 1];
for (int i = 1; i <= m; i ++ )
for (int len = i; len <= n; len ++ )
for (int j = 1; j <= n; j ++ )
{
int l = j, r = j + len - 1;
if (i == 1) f[l][r][i] = g[l][r][i] = ((a[r] - a[l - 1]) % 10 + 10) % 10;
else
{
f[l][r][i] = -INF;
g[l][r][i] = INF;
for (int k = l + i - 1; k <= r; k ++ )
{
int t = ((a[r] - a[k - 1]) % 10 + 10) % 10;
f[l][r][i] = max(f[l][r][i], f[l][k - 1][i - 1] * t);
g[l][r][i] = min(g[l][r][i], g[l][k - 1][i - 1] * t);
}
}
}
int maxv = -INF, minv = INF;
for (int i = 1; i <= n; i ++ )
{
maxv = max(maxv, f[i][i + n - 1][m]);
minv = min(minv, g[i][i + n - 1][m]);
}
cout << minv << endl << maxv << endl;
return 0;
}
2. 开心的金明
来源:NOIP2006普及组 https://ac.nowcoder.com/acm/contest/234/A
算法知识点: DP,背包问题
复杂度:&preview=true)
解题思路:
将原问题做如下转化
- 总钱数相当于背包总容量;
- 每件物品的价格相当于体积;
- 每件物品的价格乘以重要度相当于价值;
那么就变成了经典的01背包问题。
C++ 代码:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 30010;
int n, m;
int f[N];
int main()
{
cin >> m >> n;
for (int i = 0; i < n; i ++ )
{
int v, w;
cin >> v >> w;
w *= v;
for (int j = m; j >= v; j -- )
f[j] = max(f[j], f[j - v] + w);
}
cout << f[m] << endl;
return 0;
}
3. 守望者的逃离
来源:NOIP2007普及组 https://ac.nowcoder.com/acm/contest/235/C
算法知识点: DP,线性DP
复杂度: &preview=true)
解题思路:
首先比较跑步和放技能哪个更快:
- 跑步:平均一秒
米
- 放技能:平均2.5秒恢复10点魔法,再加一秒闪烁时间,一共是3.5秒可以移动60米, 所以平均每秒
米
所以放技能稍快一些。
因此当我们有充足的放技能时间时,一定要放技能,所以只有最后一小段没时间放技能的时候,才会用跑步的方式。
然后考虑如何求解:
用f[i]表示用i的时间,最多可以跑多远。
先求出只用闪烁技能时,每秒最多可以跑多远。
再用跑步的方式来“插缝”,递推出结合两种方式时,每秒最多可以跑多远。
C++ 代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 300010;
int m, s, t;
int f[N];
int main()
{
cin >> m >> s >> t;
for (int i = 1; i <= t; i ++ )
{
if (m >= 10)
{
f[i] = f[i - 1] + 60;
m -= 10;
}
else
{
f[i] = f[i - 1];
m += 4;
}
}
for (int i = 1; i <= t; i ++ ) f[i] = max(f[i], f[i - 1] + 17);
bool success = false;
for (int i = 1; i <= t; i ++ )
if (f[i] >= s)
{
printf("Yes\n%d\n", i);
success = true;
break;
}
if (!success) printf("No\n%d\n", f[t]);
return 0;
}
4. 子矩阵
来源:NOIP2014普及组 https://ac.nowcoder.com/acm/contest/242/A
算法知识点: 枚举,DP,线性DP
复杂度: &preview=true)
解题思路:
当选定的行固定时,问题变成:
给定一个长度为
的序列,从中选出一个长度为
的子序列。序列中的每个元素均有一个分值,且任意相邻两个被选出的元素,也会产生一个分值。问:如何选择子序列可使分值之和最小。
这是一个经典的序列DP模型:
状态表示:f[i][j]表示所有以第i个数结尾,且长度为j的子序列的分值之和的最小值。
状态计算:以倒数第二个数是哪个数为依据,将f[i][j]所代表的集合分成若干类,则倒数第二个数是第k个数的所有子序列的最小分值是 f[k][j - 1] + cost(),其中cost是在序列末尾加上第i个数所产生的分值。
f[i][j]取所有类别的最小分值即可。
由于 较小,我们可以直接枚举行的所有选择,然后用上述做法DP即可。
C++ 代码:
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 20, INF = 1e9;
int n, m, r, c;
int matrix[N][N];
int f[N][N];
int cw[N], rw[N][N];
int q[N];
int count(int x)
{
int s = 0;
for (int i = 0; i < n; i ++ ) s += x >> i & 1;
return s;
}
int main()
{
cin >> n >> m >> r >> c;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < m; j ++ )
cin >> matrix[i][j];
int res = INF;
for (int state = 0; state < 1 << n; state ++ )
if (count(state) == r)
{
for (int i = 0, j = 0; i < n; i ++ )
if (state >> i & 1)
q[j ++ ] = i;
for (int i = 0; i < m; i ++ )
{
cw[i] = 0;
for (int j = 1; j < r; j ++ ) cw[i] += abs(matrix[q[j]][i] - matrix[q[j - 1]][i]);
}
for (int i = 0; i < m; i ++ )
for (int j = i + 1; j < m; j ++ )
{
rw[i][j] = 0;
for (int k = 0; k < r; k ++ )
rw[i][j] += abs(matrix[q[k]][i] - matrix[q[k]][j]);
}
for (int i = 0; i < m; i ++ )
{
f[i][1] = cw[i];
for (int j = 2; j <= c; j ++ )
{
f[i][j] = INF;
for (int k = 0; k < i; k ++ )
f[i][j] = min(f[i][j], f[k][j - 1] + cw[i] + rw[k][i]);
}
res = min(res, f[i][c]);
}
}
cout << res << endl;
return 0;
}
另外,牛客暑期NOIP真题班限时免费报名
报名链接:https://www.nowcoder.com/courses/cover/live/248
报名优惠券:DCYxdCJ

京公网安备 11010502036488号