• 2022年暑假ACM热身练习3


    目录

    夹角有多大

    汉诺塔3/4

    选课时间

    龟兔赛跑

    Subset sequence

    红色病毒问题

    一个人的旅行

    小兔的棋盘

    RPG的错排

    Coin Change


    夹角有多大

    translation: 

            给定时、分、秒,要求你求出时针和分针之间的角度。

    problem:

    1. 时分秒也要实时匹配,也就是说,分钟走一点,也是会影响是针的角度的。同理,秒针也是。
    2. 当时卡在了角度要保证在【0,180】之间。假如时针走的很多,分针走的很少,那他们之间的角度比如为30度,但如果以圆点到 0 点位起始轴计算角度的话,那计算出来的结果是330,此时我想的是对180取模,可这样是得到的是150呀,那30和150之间还有转换,这....   反正对角度的问题很菜就是了..

    当角度k大于180时, k = 360 - k 

    1. // problem :
    2. #include
    3. using namespace std;
    4. #define ll long long
    5. typedef pair<int, int> PII;
    6. #define pb push_back
    7. int main(){
    8. int t; scanf("%d", &t);
    9. while(t--){
    10. double h, m, s; scanf("%lf %lf %lf", &h, &m, &s);
    11. if(h >= 12)h -= 12;
    12. double k1 = (h + m / 60 + s / 3600) * 360 / 12;
    13. double k2 = (m + s / 60) * 360 / 60;
    14. double ans = fabs(k1 - k2);
    15. if(ans > 180)ans = 360 - ans;
    16. int res = ans;
    17. printf("%d\n", res);
    18. }
    19. return 0;
    20. }

    汉诺塔3/4

    汉诺塔4是在3的基础上添加了额外条件,在此先介绍一下汉诺塔3

       A          B          C 

     ——     ——     ——

    做递归的题,找对递归表达的状态是最关键的一步。

    比如这题的。

    设 solve(n) 表示 :   将A盘上的n个盘子借助 B 盘 放到 C盘   (其中AC盘是可以互换的)

    那么问题的求解可以表示为: solve(n) = solve(n - 1) + 1 + solve(n - 1) + 1 + solve(n - 1)

    其中的 + 1 操作是移动最底下的那个盘子。

    1. // problem :
    2. #include
    3. using namespace std;
    4. #define ll long long
    5. typedef pair<int, int> PII;
    6. #define pb push_back
    7. ll solve(int n){
    8. ll ans = 0;
    9. if(n == 1)return 2;
    10. ll k = solve(n - 1);
    11. ans += k;
    12. ans++;
    13. ans += k;
    14. ans++;
    15. ans += k;
    16. return ans;
    17. }
    18. int main(){
    19. int n;
    20. while(~scanf("%d", &n)){
    21. printf("%lld\n", solve(n));
    22. }
    23. return 0;
    24. }

    汉诺塔3会做,汉诺塔4多了个条件,就不会了(ಥ﹏ಥ)

    translation:

            多了个条件,允许最大的盘子放在最上面。这里的题意(是指n个里最大,还是那一堆里最大的可以放最上面???,反正但是是挺疑惑)

    problem:

            这里只有第n个盘子能放在其他盘子上面, 比如n=5  , 有一堆是  1 3  2     那3是不能放在最上面的!    所以我们移动上面的盘子时,还是要用 汉诺塔3的代码。

            用递归其实复杂度是不高的,也是O(n)的时间复杂度。而且还好理解很多,只不过代码篇幅可能较长 ( 那也只是我一步一步写,完全可以把代码合并

    1. // problem :
    2. #include
    3. using namespace std;
    4. #define ll long long
    5. typedef pair<int, int> PII;
    6. #define pb push_back
    7. ll solve1(int n){
    8. if(n == 1)return 2;
    9. ll ans = 0;
    10. ll k = solve1(n - 1);
    11. ans += k;
    12. ans++;
    13. ans += k;
    14. ans++;
    15. ans += k;
    16. return ans;
    17. }
    18. ll solve2(int n){
    19. if(n == 1)return 2;
    20. if(n == 2)return 4;
    21. ll ans = 0;
    22. ll k = solve1(n - 2);
    23. ans += k;
    24. ans += 2;
    25. ans += k;
    26. ans += 2;
    27. ans += k;
    28. return ans;
    29. }
    30. int main(){
    31. int t; scanf("%d", &t);
    32. while(t--){
    33. int n; scanf("%d", &n);
    34. printf("%lld\n", solve2(n));
    35. }
    36. return 0;
    37. }

    选课时间

    problem:

             搜索、背包?  感觉偏背包的可能性较大

    solve:多重背包问题,但又有点特殊     要先枚举背包容量,否则会重复

     先给出AC代码:

    1. // problem :
    2. #include
    3. using namespace std;
    4. #define ll long long
    5. typedef pair<int, int> PII;
    6. #define pb push_back
    7. int f[45];
    8. int main(){
    9. int t; scanf("%d", &t);
    10. while(t--) {
    11. int n, k; scanf("%d %d", &n, &k);
    12. memset(f, 0, sizeof(f));
    13. f[0] = 1;
    14. while(k--) {
    15. int a, b; scanf("%d %d", &a, &b);
    16. for(int j = n; j >= a; --j)
    17. for(int m = 1; m <= b && j >= m * a; ++m)
    18. f[j] += f[j - a * m];
    19. }
    20. cout << f[n] << endl;
    21. }
    22. return 0;
    23. }

    为什么说特殊呢?因为这题不能用二进制拆分优化!

    1. for (int i = 1; i <= n; i++)
    2. for (int j = V; j >= w[i]; j--)
    3. for (int k = 1; k <= s[i] && k * w[i] <= j; k++)
    4. f[j] = max(f[j], f[j - w[i] * k] + v[i] * k);

    先枚举背包的容量,然后每种情况都放一遍        可以达到多重背包的效果。 

    先看看二进制拆分的代码

    1. for(int i = 1; i <= n; ++i){
    2. int num = min(s[i], V / w[i]);
    3. for(int k = 1; num; k <<= 1){
    4. if(k > num)k = num;
    5. num -= k;
    6. for(int j = V; j >= k * w[i]; --j)
    7. f[j] = max(f[j], f[j - k * w[i]] + k * v[i]);
    8. }
    9. }

    这里是先枚举二进制数,再遍历背包的容量。V这个背包先放入1个,  下一轮2个...    ,最终1+2+...+2^n + k       达到背包存放的最大个数(num)。 为什么二进制数会叠加?因为较小的背包已经放入了物品。

    但我们“选课时间”这个题目是要先枚举背包的,要不然会有重复计算。

    那我们可以写先遍历背包,再枚举数量的二进制优化吗?答案是不行的。

    1. for(int i = 1; i <= n; ++i){
    2. for(int j = V; j >= w[i]; --j){
    3. int num = min(s[i], j / w[i]);
    4. for(int k = 1; num; k <<= 1){
    5. if(k > num)k = num;
    6. num -= k;
    7. f[j] = max(f[j], f[j - k * w[i]] + k * v[i]);
    8. }
    9. }
    10. }

     这份代码达到的是     V 这个背包容量 放1个能不能最大, 2 个 ……  2^n 个,能不能最大。但单个的2^n   又不能组成 num  个, 所以是不行的。

    龟兔赛跑

    problem: 一开始的想法是贪心,但要怎么贪呢?很关键的一点就是到达一个充电站之后,可以选中充电,或者选中不充电。当时没有去做,毕竟连考题的是贪心还是DP都没分清。

    solve: 本题用DP解决。关键点还是充电站要不要充电问题。还是感觉很巧妙吧!

    设  f[i] 为到第i个站点所花费的最少时间。接下来要确定状态转移方程;到第i个点,那肯定也经过了之前的点,那么  f[i]  = f[j] + time(i,j);     可是这怎么体现充电站要不要充电问题? 

    j --> i  ,中间可能有站,所以中间的站就是不充电的站,j是充电的(除了起始点)。

    分析完也不难,关键就是:

    1. 先清楚这是个DP题;
    2. f[i] 代表什么意思;
    3. 列出状态转移方程。
    1. // problem :
    2. #include
    3. using namespace std;
    4. #define ll long long
    5. typedef pair<int, int> PII;
    6. #define pb push_back
    7. int l, n, c, t, p[105];
    8. double vr, vt1, vt2;
    9. double f[105];
    10. int main(){
    11. while(~scanf("%d", &l)) {
    12. scanf("%d %d %d", &n, &c, &t);
    13. scanf("%lf %lf %lf", &vr, &vt1, &vt2);
    14. for(int i = 1; i <= n; ++i)
    15. scanf("%d", &p[i]);
    16. p[n + 1] = l;
    17. memset(f, 0, sizeof(f));
    18. f[1] = 0;
    19. for(int i = 2; i <= n + 1; ++i){
    20. double minn = 1 << 30;
    21. for(int j = 1; j < i; ++j){
    22. double time = f[j];
    23. if(j != 1)
    24. time += t;
    25. int len = p[i] - p[j];
    26. if(len > c)
    27. time += c * 1.0 / vt1 + (len - c) * 1.0 / vt2;
    28. else
    29. time += len * 1.0 / vt1;
    30. minn = min(minn, time);
    31. }
    32. f[i] = minn;
    33. }
    34. double res = l * 1.0 / vr;
    35. if(res < f[n + 1])puts("Good job,rabbit!");
    36. else puts("What a pity rabbit!");
    37. }
    38. return 0;
    39. }

    Subset sequence

    红色病毒问题

    problem: 只能说是毫无头绪

    solve:涉及什么生成函数,直接记住公式了。    4^(n-1) + 2^(n-1)

    1. // problem :
    2. #include
    3. using namespace std;
    4. #define ll long long
    5. typedef pair<int, int> PII;
    6. #define pb push_back
    7. const int p = 100;
    8. // ans=4^(n-1)+2^(n-1)
    9. ll rp(ll now, ll k){
    10. ll res = 1;
    11. for(; k; k >>= 1, now *= now, now %= p)
    12. if(k & 1)
    13. res *= now, res %= p;
    14. return res;
    15. }
    16. int t;
    17. int main(){
    18. while(~scanf("%d", &t) && t) {
    19. for(int i = 1; i <= t; ++i){
    20. ll n; scanf("%lld", &n);
    21. printf("Case %d: %lld\n", i,(rp(4, n - 1) + rp(2, n - 1)) % p);
    22. }
    23. putchar(10);
    24. }
    25. return 0;
    26. }

    一个人的旅行

     这是个多起点,多重点的问题,而且,按照题目的样例,家离起点的距离都是0,那么我们就可以设置一个超级起点,这个起点到普通起点连一条权重为0的边,以超级起点为dijkstra搜索的起点即可。就算家里各个起点的距离不为0,甚至各不相同,也可以采用这个方法。(先假设距离都是0,再将最终算得的距离加上家离起点最短的距离即可。)

    problem:写代码时,  memset(head, 0, sizeof(127));   导致超时多次,

            其次,还有一种题型是  在地图上找一个点,求到  p1、p2、p3...pn  的最短路径和

    要对每个pi  做一次dijkstra

    1. // problem :
    2. #include
    3. using namespace std;
    4. #define ll long long
    5. typedef pair<int, int> PII;
    6. #define pb push_back
    7. int t, s, d;
    8. struct edge{
    9. int to, w, next;
    10. }e[100005];
    11. int cnt, head[1005], dis[1005];
    12. inline void makelist(int u, int v, int w){
    13. e[++cnt].to = v;
    14. e[cnt].next = head[u];
    15. e[cnt].w = w;
    16. head[u] = cnt;
    17. }
    18. struct node{
    19. int id, _dis;
    20. bool operator < (const node & a) const {
    21. return _dis > a._dis;
    22. }
    23. };
    24. bool vis[1005];
    25. void dijkstra(int _s){
    26. dis[_s] = 0;
    27. priority_queue q;
    28. q.push({_s, dis[_s]});
    29. while(!q.empty()){
    30. node k = q.top();
    31. q.pop();
    32. int id = k.id;
    33. if(vis[id])continue;
    34. vis[id] = true;
    35. for(int x = head[id]; x; x = e[x].next){
    36. int to = e[x].to;
    37. int w = e[x].w;
    38. if(vis[to])continue;
    39. if(w + dis[id] < dis[to]){
    40. dis[to] = w + dis[id];
    41. q.push({to, dis[to]});
    42. }
    43. }
    44. }
    45. }
    46. void init(){
    47. memset(vis, false, sizeof(vis));
    48. memset(dis, 127, sizeof(dis));
    49. memset(head, 0, sizeof(head));
    50. cnt = 0;
    51. }
    52. int main(){
    53. while(~scanf("%d %d %d", &t, &s, &d)){
    54. init();
    55. for(int i = 1; i <= t; ++i){
    56. int x, y, time; scanf("%d %d %d", &x, &y, &time);
    57. makelist(x, y, time);
    58. makelist(y, x, time);
    59. }
    60. for(int i = 1; i <= s; ++i){
    61. int x; scanf("%d", &x);
    62. makelist(1001, x, 0);
    63. }
    64. int dd[1005];
    65. for(int i = 1; i <= d; ++i)
    66. scanf("%d", &dd[i]);
    67. int ans = 1 << 30;
    68. dijkstra(1001);
    69. for(int i = 1; i <= d; ++i)
    70. ans = min(ans, dis[dd[i]]);
    71. printf("%d\n", ans);
    72. }
    73. return 0;
    74. }

    小兔的棋盘

    problem: 本身问题是不难的,甚至还有点简单,但就是样例给出的数据输出有点大病。

            姑且认为是考察观察能力了。    output:   第几个测试样例     棋盘大小    答案

    1. // problem :
    2. #include
    3. using namespace std;
    4. #define ll long long
    5. typedef pair<int, int> PII;
    6. #define pb push_back
    7. int n;
    8. ll b[40][40];
    9. ll dfs(int x, int y){
    10. if(b[x][y])return b[x][y];
    11. if(x == n || y == n)return 1;
    12. ll ans = 0;
    13. ans += dfs(x + 1, y);
    14. if(y < x)
    15. ans += dfs(x, y + 1);
    16. return b[x][y] = ans;
    17. }
    18. int main(){
    19. int c = 1;
    20. while(~scanf("%d", &n)){
    21. memset(b, 0, sizeof(b));
    22. if(n == -1)break;
    23. printf("%d %d %lld\n",c++, n, 2 * dfs(0, 0));
    24. }
    25. return 0;
    26. }

    RPG的错排

     problem:怎么说呢,这题错细节上了,有两个地方没有注意到

    1. factorial[21]  就会爆 long long     排列组合时,要先除
    2. m个人选对,那么就有n-m个人全错排,但是给写成了 m个人全错排

    solve:

            需要注意,选对n - 1个人  和 选对 n 个人是同一种情况,所以我们直接舍弃这两种情况,最后加1即可。

    1. // problem :
    2. #include
    3. using namespace std;
    4. #define ll long long
    5. typedef pair<int, int> PII;
    6. #define pb push_back
    7. ll f[30];
    8. ll factorial[30];
    9. void solve(){
    10. // 阶乘
    11. factorial[0] = 1;
    12. for(int i = 1; i <= 25; ++i)
    13. factorial[i] = factorial[i - 1] * i;
    14. // 全错排公式
    15. f[1] = 0, f[2] = 1;
    16. for(int i = 3; i <= 25; ++i)
    17. f[i] = (i - 1) * (f[i - 1] + f[i - 2]);
    18. }
    19. int main(){
    20. int n;
    21. solve();
    22. while(~scanf("%d", &n) && n){
    23. int k;
    24. if(n & 1) k = (n+1)/2;
    25. else k = n / 2;
    26. ll ans = 0;
    27. for(int m = k; m <= n - 2; ++m){
    28. // ans += factorial(n)/factorial(n - m)/factorial(m) * f[n - m];
    29. ll c = 1;
    30. for(int i = m + 1; i <= n; ++i)
    31. c *= i;
    32. // c = factorial(n) / factorial(m) ---> (m + 1) ... n 各位相乘
    33. ans += c / factorial[n - m] * f[n - m];
    34. }
    35. ans++;
    36. printf("%lld\n", ans);
    37. }
    38. return 0;
    39. }

    Coin Change

     题目大概就是跟找零钱花费最少的硬币差不多,  不过这里的题意是要求你求出有多少种凑够零钱的方式,并且所花费的硬币数量不能超过100个。

     没看到题目wa了的代码:

    1. void solve(){
    2. f[0] = 1;
    3. for(int i = 0; i < 5; ++i)
    4. for(int j = type[i]; j <= 250; ++j)
    5. f[j] += f[j - type[i]];
    6. }

    AC代码:(多加了一维数组,来限定条件)

    1. // problem :
    2. #include
    3. using namespace std;
    4. #define ll long long
    5. typedef pair<int, int> PII;
    6. #define pb push_back
    7. int type[5] = {1, 5, 10, 25, 50};
    8. int f[255][105];
    9. void solve(){
    10. f[0][0] = 1;
    11. for(int i = 0; i < 5; ++i)
    12. for(int j = 1; j <= 100; ++j)
    13. for(int k = type[i]; k <= 250; ++k)
    14. f[k][j] += f[k - type[i]][j - 1];
    15. }
    16. int main(){
    17. int n;
    18. solve();
    19. while(~scanf("%d", &n)){
    20. int ans = 0;
    21. for(int i = 0; i <= 100; ++i)
    22. ans += f[n][i];
    23. printf("%d\n", ans);
    24. }
    25. return 0;
    26. }
  • 相关阅读:
    Docker将镜像文件发布到私服库
    mysql基础知识篇(一)
    关于产品和消费的思考
    算法刷题日志——二叉树
    深度理解实分析:超越公式与算法的学习方法
    TCP/IP(十九)TCP 实战抓包分析(三)TCP 第一次握手 SYN 丢包
    RabbitMQ初识以及简单模式初步
    无法启动报,To install it, you can run: npm install --save @/components/iFrame/index
    计算机组成原理 | 中央处理器
    虚幻C+++基础 day2
  • 原文地址:https://blog.csdn.net/PURSUE__LIFE/article/details/125874043