问题 A: 没有上司的舞会
题目描述
Ural大学有N名职员,编号为1~N。
他们的关系就像一棵以校长为根的树,父节点就是子节点的直接上司。
每个职员有一个快乐指数,用整数 HiHi 给出,其中 1≤i≤N。
现在要召开一场周年庆宴会,不过,没有职员愿意和直接上司一起参会。
在满足这个条件的前提下,主办方希望邀请一部分职员参会,使得所有参会职员的快乐指数总和最大,求这个最大值。
输入
第一行一个整数N。
接下来N行,第 i 行表示 i 号职员的快乐指数Hi。
接下来N-1行,每行输入一对整数L, K,表示K是L的直接上司。
最后一行输入0,0。
输出
输出最大的快乐指数。
样例输入 复制
7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5
0 0
样例输出 复制
5
提示
1≤N≤6000,
思路:经典的树形dp
对于每一个节点,都有两个选择,选与不选
f[i][0] : 表示第 i 个 节点不选 对应的最大值
f[i][1] : 表示第 i 个 节点选 对应的最大值
如果该父节点选,那么子节点一定不能选
如果不选, 那么子节点 可以选或者不选
则
u 为父节点, j 为子节点
f[u][0] += max(f[j][0], f[j][1])
f[u][1] += f[j][0]
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 6005;
int h[N], ne[N], e[N], idx;
bool st[N];
int a[N], f[N][2];
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
void dfs(int u) {
f[u][1] = a[u];
for(int i = h[u]; i != -1; i = ne[i]) {
int j = e[i];
dfs(j);
f[u][0] += max(f[j][1], f[j][0]);
f[u][1] += f[j][0];
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
memset(h, -1, sizeof h);
int n;
cin >> n;
for(int i = 1; i <= n; ++ i) cin >> a[i];
for(int i = 1; i < n; ++ i) {
int a, b;
cin >> a >> b;
add(b, a);
st[a] = true;
}
int root = 1;
while(st[root]) ++ root;
dfs(root);
cout << max(f[root][0], f[root][1]) << "\n";
return 0;
}
问题 B: Fibonacci
题目描述
斐波那契数列的性质为:
F0=0,F1=1,Fn=Fn−1+Fn−2。
同时,通过线性代数的学习,我们知道矩阵乘法的运算时符合结合律的。
因此,求Fn mod 104。
输入
多组数据,每组数据一行,一个整数n。
输入以-1结束。
输出
对于每组数据,输出Fn mod 104。
样例输入 复制
0
9
999999999
1000000000
-1
样例输出 复制
0
34
626
6875
提示
对于全部数据,0≤n≤10^9。
方法:矩阵快速幂 加速
Fi, Fi + 1\] \*\[ 0 , 1 \] = \[Fi + 1, F i + 2
1 , 1
利用矩阵的结合律,利用快速幂加速整个进程
cpp
#include <bits/stdc++.h>
using namespace std;
const int mod = 10000;
vector<vector<int>> mul(vector<vector<int>> a, vector<vector<int>> b, int mod) {
int n = a.size(), m = b[0].size(), p = b.size();
vector<vector<int>> ans(n, vector<int> (m, 0));
for(int i = 0; i < n; ++ i) {
for(int j = 0; j < m; ++ j) {
for(int k = 0; k < p; ++ k)
ans[i][j] = (ans[i][j] + a[i][k] * b[k][j]) % mod;
}
}
return ans;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int n;
while(cin >> n, n != -1) {
if(n == 0) cout << 0 << "\n";
else {
vector<vector<int>> a = {
{0, 1},
{1, 1}
};
vector<vector<int>> f = {
{1, 1}
};
-- n;
while(n) {
if(n & 1) f = mul(f, a, mod);
a = mul(a, a, mod);
n >>= 1;
}
cout << f[0][0] % mod << "\n";
}
}
return 0;
}
问题 C: 超级楼梯
题目描述
有一个楼梯共M级台阶,刚开始你在第一级,若每次只能挂上1或2级,要走上M级,共有多少种走法?
输入
首先包含一个整数N,表示测试实例的个数,然后是N行数据,每行一个M,表示楼梯级数
输出
对于每个实例 输出不同走法的数量
样例输入 复制
2
2
3
样例输出 复制
1
2
提示
1≤M≤40
思路:
状态表示:
f[i] : 表示 走到 第 i 层楼梯 的方案数
状态方程转移:
f[i] += f[i - 1] + f[i - 2]
初始化:
f[1] = 1
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 50;
int f[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int T;
cin >> T;
while(T --) {
int n;
cin >> n;
memset(f, 0, sizeof f);
//f[0] = 1;
f[1] = 1;
for(int i = 2; i <= n; ++ i) {
f[i] += f[i - 1] + f[i - 2];
}
cout << f[n] << "\n";
}
return 0;
}
问题 D: 进阶7.4.3最长上升子序列
题目描述
A numeric sequence of ai is ordered if a1 < a2 < ... <aN. Let the subsequence of the given numeric sequence (a1,a2, ...,aN) be any sequence (ai1,ai2, ...,aiK), where 1 <=i1 < i2 < ... <iK <=N. For example, sequence (1, 7, 3, 5, 9, 4, 8) has ordered subsequences, e. g., (1, 7), (3, 4, 8) and many others. All longest ordered subsequences are of length 4, e. g., (1, 3, 5, 8).
Your program, when given the numeric sequence, must find the length of its longest ordered subsequence.
输入
The first line of input file contains the length of sequence N. The second line contains the elements of sequence - N integers in the range from 0 to 10000 each, separated by spaces. 1 <= N <= 1000
输出
Output file must contain a single integer - the length of the longest ordered subsequence of the given sequence.
样例输入 复制
7
1 7 3 5 9 4 8
样例输出 复制
4
思路:
f[i] : 表示 以第 i 个元素 结尾的上升子序列的长度
f[i] = 1;
for(int j = 1; j < i; ++ j) {
if(a[i] > a[j]) f[i] = max(f[i], f[j] + 1);
}
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1005;
int f[N];
int a[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int n;
cin >> n;
for(int i = 1; i <= n; ++ i) cin >> a[i];
int ans = 1;
for(int i = 1; i <= n; ++ i) {
f[i] = 1;
for(int j = 1; j < i; ++ j) {
if(a[i] > a[j]) f[i] = max(f[i], f[j] + 1);
}
ans = max(ans, f[i]);
}
cout << ans << "\n";
return 0;
}
问题 E: 进阶7.2.1 骨头收藏家
题目描述
有位骨头收藏家喜欢收集各种各样的骨头,不同的骨头有不同的体积和价值。这个收藏家有一个体积为V的背包,请计算他可以收藏的最大价值。
输入
第1行包含一个整数T,表示测试用例的数量。每个测试用例都包含3行,第1行包含两个整数N、V(N<=1000,V<=1000),分别表示骨头的数量和背包的体积;第2行包含N个整数,表示每个骨头的价值;第3行包含N个整数,表示每个骨头的体积。
输出
对每个测试用例,都单行输出可以得到的最大价值(该数小于231)。
样例输入 复制
1
5 10
1 2 3 4 5
5 4 3 2 1
样例输出 复制
14
思路:
经典的背包问题, 体积最大为 v 的背包所能携带的最大的价值
f[i] : 表示为 体积最大为 i 时的对应的最大价值
for(int j = v; j >= w[i]; -- j)
f[j] = max(f[j], f[j - w[i]] + v[i])
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1005;
int v[N], w[N];
int f[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int T;
cin >> T;
while(T --) {
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; ++ i) cin >> v[i];
for(int i = 1; i <= n; ++ i) cin >> w[i];
for(int i = 1; i <= n; ++ i) {
for(int j = m; j >= w[i]; -- j)
f[j] = max(f[j], f[j - w[i]] + v[i]);
}
cout << f[m] << "\n";
}
return 0;
}
问题 F: 到处都是0
题目描述
求1到N(包括1和N)之间的整数的数目,
这些整数恰好包含K个非0数字
输入
N
K
1 <= N <= 10100
1 <= K <= 3
输出
输出一行结果
样例输入 复制
100
1
样例输出 复制
19
提示
- 1,2,3,4,5,6,7,8,9,10,20,30,40,50,60,70,80,90,100
样例2
9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
3
输出
117879300
思路:
经典的数位dp问题
注意此题的细节, N 的值过大,需要用字符串输入!!!
f[i][j] : 表示从 i 个位置 中选择 j 个的可能数
cpp
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 105;
ll f[N][N];
void init() {
for(int i = 0; i < N; ++ i) f[i][0] = 1;
for(int i = 1; i < N; ++ i) {
for(int j = 1; j <= i; ++ j) {
f[i][j] = f[i - 1][j] + f[i - 1][j - 1];
}
}
}
int dp(string s, int k) {
int n = s.size();
ll ans = 0;
int last = 0;
for(int i = 0; i < n; ++ i) {
int x = s[i] - '0';
if(x) ans += (ll) f[n - i - 1][k - last] * pow(9, k - last);
if(last <= k - 1) {
for(int j = 1; j < x; ++ j) {
ans += (ll) f[n - i - 1][k - last - 1] * pow(9, k - last - 1);
}
}
if(x) ++ last;
if(last > k) break;
if(i == n - 1 && k == last) ++ ans;
}
return ans;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
string s;
int k;
cin >> s >> k;
init();
cout << dp(s, k) << "\n";
return 0;
}
问题 G: 吉吉想玩游戏
题目描述
吉吉想购买一个游戏机。
有N家商店出售她想要的游戏机:商店1、2、...、N。商店i距离吉吉现在的位置有Ai分钟的步行路程,售价为Pi元,当前库存中有Xi台游戏机。
现在,吉吉打算步行去其中一家商店购买游戏机,前提是当他到达商店时商品还有库存。
然而,这种游戏机非常受欢迎,因此每家商店的库存数量(如果有)将在以下时间点减少1台:0.5、1.5、2.5分钟后。
确定吉吉是否能买到游戏机。如果可以,找出购买一台所需的最低金额。
快帮助吉吉低价购入一台游戏机吧!
输入
所有输入都是数字
1≤N≤105
1≤Ai,Pi,Xi≤109
输出
如果吉吉能买到游戏机,以整数形式打印购买一台所需的最低金额。
如果她无法购买,则打印-1。
样例输入 复制
3
3 9 5
4 8 5
5 7 5
样例输出 复制
8
思路:
分析完题意后,可知
只有当路程时间小于库存数量,才能获得这个物品
cpp
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 100005;
int a[N], b[N], c[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int n;
cin >> n;
for(int i = 1; i <= n; ++ i) cin >> a[i] >> b[i] >> c[i];
int ans = 1e9 + 1;
for(int i = 1; i <= n; ++ i) {
if(a[i] < c[i]) {
ans = min(ans, b[i]);
}
}
if(ans == 1e9 + 1) cout << -1 << "\n";
else cout << ans << "\n";
return 0;
}