思路:
因为这里零钱设置是成倍数的,所以可以用贪心算法来做,每次选择最大的,
但是如果零钱不是成倍数的,或者是随机输入的,就不能用贪心做,要用动态规划来做。
eg:给零钱[1,7,10],要凑14块钱,
如果是贪心,就是14=10+1+1+1,共4张,但是这不是最优的
最优应该是14=7+7
代码:
#include <iostream>
using namespace std;
const int MaxNum = 1e2 + 7;
int money[6] = {100, 50, 10, 5, 2, 1};// 因为钱数设置的特点,所以不用用动态规划QAQ
int teachers[MaxNum];
// 计算每个教师需要几张人民币
int cal(int m) {
int num = 0;
// 从大到小,遍历钱数,计算需要几张
for (int i = 0; i < 6; i++) {
int cnt = m / money[i];
num += cnt;
m -= money[i] * cnt;
if (m == 0) {
break;
}
}
return num;
}
int main() {
int n;
while (true) {
cin >> n;
if (n == 0) {
break;
}
int sum = 0;
for (int i = 0; i < n; i++) {
cin >> teachers[i];
sum += cal(teachers[i]);
}
cout << sum << endl;
}
return 0;
}
注意dp[1]=1,而不是dp[0]=0,因为一开始就在第一级上,只有一种解法。
代码:
#include <iostream>
#include <cstring>
using namespace std;
const int MaxNum = 41;
int dp[MaxNum]; //dp[i]表示走到第i级,一共有多少中走法
// 计算
void f() {
dp[1] = 1; // 刚开始就是在第一级,只有一种走法
dp[2] = 1; // 从第一级走一步,只有一种走法
for (int i = 3; i < MaxNum; i++) {
dp[i] = dp[i - 1] + dp[i -2];
}
}
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) {
int m;
cin >> m;
f();
cout << dp[m] << endl;
}
return 0;
}
我的方法:递归求幂,但是会超时;转为递推就不会。
简单方法:求2的n次幂,1左移n次即可
代码:
#include <iostream>
using namespace std;
const int MaxNum = 41;
int dp[MaxNum]; //dp[i]表示走到第i级,一共有多少中走法
// 递归计算,会超时QAQ
long long f(int a, int n) {
if (n == 1) {
return a;
}
if (n % 2 == 1) {
return a * f(a, n - 1);
}
return f(a * a, n / 2);
}
// 递推,不会超时
long long f1(int a, int n) {
int ans = 1;
while (n > 0) {
if (n % 2 == 1) {
ans = ans * a;
n--;
} else {
a = a * a;
n = n / 2;
}
}
return ans;
}
// 最简单的方法,只不过只能计算2的n次方
long long f3(int n) {
return (1 << n);
}
int main() {
int n;
cin >> n;
cout << f3(n) << endl;
return 0;
}
因为n最大为1e4,对每个数判断是否包含那4个数字,时间复杂度是o(4*n),
由于范围不大,所以我们可以直接暴力一个区间内所有的数,然后对每个数的每一位判断,判断出一些满足要求的数,最后输出所有满足要求的数的和就行了。
日期应该从本年开始枚举QAQ;
然后是输出的时候,要按照日期的格式输出
代码:不知道为什么有一个例子没有通过
#include <iostream>
using namespace std;
int days[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
// 判断一个日期是否合法
bool check(int y, int m, int d, int n) {
int y1 = n / 10000;
int m1 = n / 100 % 100;
int d1 = n % 100;
// 如果当前日期与回文日期同年,要判断回文日期是否在当前日期的后面
if (y == y1) {
if (y * 10000 + m * 100 + d <= y1 * 10000 + m1 * 100 + d1) {
return false;
}
}
if (m < 1 || m > 12) {
return false;
}
if (m == 2 && ((y % 400 == 0) || (y % 4 == 0 && y % 100 != 0))) {
if (d < 1 || d > days[2] + 1) {
return false;
}
return true;
}
if (d < 1 || d > days[m]) {
return false;
}
return true;
}
// 找下一个回文日期
void f1(int n) {
// 应该从当前年份开始枚举,因为当前输出的日期不是回文日期,那么与它同年的可能还有回文日期
for (int i = n / 10000; i < 9999; i++) {
int y1 = i;
int a[5];
for (int j = 4; j > 0; j--) {
a[j] = y1 % 10;
y1 = y1 / 10;
}
int m = a[4] * 10 + a[3];
int d = a[2] * 10 + a[1];
int y = i;
if (check(y, m, d, n)) {
printf("%04d%02d%02d\n", y, m, d);
return;
}
}
}
// 找下一个ab日期
void f2(int n) {
for (int i = n / 1000000; i < 99; i++) {
int a[2];
a[0] = i / 10;
a[1] = i % 10;
int m = a[1] * 10 + a[0];
int d = m;
int y = i * 100 + i;
if (check(y, m, d, n)) {
// cout << y << m << d;
printf("%04d%02d%02d\n", y, m, d);// 要格式化输出日期,不足两位高位补0
return;
}
}
}
int main() {
int n;
cin >> n;
// 本来的想法是按照年来枚举,但是枚举的第一年应该设为同一年,找到第一个回文日期,不仅要判断是否合法,还要判断是否在输入日期的后面
f1(n);
cout<<"aaaaaaaaaaaa"<<endl;
f2(n);
return 0;
}
dp[i]表示以i开头的数列有几个
代码:
#include <iostream>
using namespace std;
const int maxN = 1e3 + 7;
int dp[maxN];//dp[i]表示以i开头的数列有几个
int main() {
int n;
cin >> n;
dp[1] = 1;
for (int i = 2; i <= n; i++) {
dp[i] = 1;
for (int j = 1; j <= i / 2; j++) {
dp[i] += dp[j];
}
}
cout << dp[n] << endl;
return 0;
}
从n个数中选择k个数,用dfs的排列树实现
难点:在全排列中怎么不选到重复的序列?比如1,2,3和3,2,1在全排列中是不一样的,但是在这题中是一样的。
方法:用不降原则,每次选的数下标 在前一个下标的后面,这样就不会出现重复的了。
所以我们可以运用不降原则:
保证枚举的这些数是升序排列
代码:
#include <iostream>
using namespace std;
const int maxN = 1e3 + 7;
int n, k;
int cnt;
int arr[maxN];
// 判断是否是素数
bool isPrim(int n) {
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) {
return false;
}
}
return true;
}
// step指当前选第几个数,preIndex指上一个被选中的数的下标,sum指当前已经选好的数的和
void dfs(int step, int preIndex, int sum) {
if (step > k) {
if (isPrim(sum)) {
cnt++;
}
return;
}
// 从上一个数的后面选择第step个数,这样选出来的数字下标是升序的,就不会重复了
for (int i = preIndex + 1; i <= n; i++) {
dfs(step + 1, i, sum + arr[i]);
}
}
int main() {
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> arr[i];
}
dfs(1,0,0);
cout << cnt << endl;
return 0;
}
求最小生成树,用kruskal算法比较简单
代码:
#include<iostream>
#include <algorithm>
using namespace std;
const int maxN = 3e3 + 7;
int n, m;
typedef struct Edge {
int v1, v2;
int weight;
bool operator<(const Edge &b) const {
return weight < b.weight;
}
} Edge;
Edge edges[maxN]; //图的边表存储
int vset[maxN]; //并查集
int sum; // 最小生成树边数之和
// 找到元素x所在并查集的根节点,并加上路径压缩:所经过的路径上的节点的父亲都指向根
int find(int x) {
int tmp = x;
// 先找根
while (x != vset[x]) {
x = vset[x];
}
// 压缩路径
while (tmp != x) {
int f = vset[tmp];
vset[tmp] = x;
tmp = f;
}
return x;
}
// 合并两个元素
void unionSet(int x, int y) {
int f1 = find(x);
int f2 = find(y);
if (f1 == f2) {
return;
}
if (f1 > f2) { // 将数字小的合并到数字大的中去
vset[f2] = f1;
} else {
vset[f1] = f2;
}
}
// kruskal算法求最小生成树,用边表存储
int kruskal() {
int sum = 0;
sort(edges, edges + m);
for (int i = 0; i < m; i++) {
Edge e = edges[i];
// 判断它的两个端点是否在一个并查集中,如果不在,就可以选,如果在,就不可以选
int v1 = e.v1, v2 = e.v2;
int f1 = find(v1);
int f2 = find(v2);
if (f1 == f2) {
continue;
}
// 不在一个集合中,可以选
sum += e.weight;
// 合并两个集合
if (f1 > f2) { // 将数字小的合并到数字大的中去
vset[f2] = f1;
} else {
vset[f1] = f2;
}
}
return sum;
}
int main() {
cin >> n >> m;
for (int i = 0; i < m; i++) {
cin >> edges[i].v1 >> edges[i].v2 >> edges[i].weight;
// 可能出现重边或者自环,
// 但是kruskal算法是按照边大小排序的,
// 有重边的话也只会选择最小的那个,自环如果点加入集合中,就不会再选一遍
}
// 对并查集进行初始化
for (int i = 0; i <= n; i++) {
vset[i] = i; // 父亲是自己,说明是并查集的树根
}
cout << kruskal() << endl;
return 0;
}