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 

// problem :  
 
#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef pair<int, int> PII;
#define pb push_back
int main(){
    int t; scanf("%d", &t);
    while(t--){
        double h, m, s; scanf("%lf %lf %lf", &h, &m, &s);
        if(h >= 12)h -= 12;
 
        double k1 = (h + m / 60 + s / 3600) * 360 / 12;
        double k2 = (m + s / 60) * 360 / 60; 
 
        double ans = fabs(k1 - k2);
        if(ans > 180)ans = 360 - ans;
        int res = ans;
        printf("%d\n", res);
    }
 
    return 0;
}

汉诺塔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 操作是移动最底下的那个盘子。

// problem :  

#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef pair<int, int> PII;
#define pb push_back

ll solve(int n){
    ll ans = 0;
    if(n == 1)return 2;
    ll k = solve(n - 1);
    ans += k;
    ans++;
    ans += k;
    ans++;
    ans += k;
    return ans;
}

int main(){
    int n;
    while(~scanf("%d", &n)){
        printf("%lld\n", solve(n));
    }

    return 0;
}

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

translation:

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

problem:

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

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

// problem :  

#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef pair<int, int> PII;
#define pb push_back


ll solve1(int n){
    if(n == 1)return 2;
    ll ans = 0;
    ll k = solve1(n - 1);
    ans += k;
    ans++;
    ans += k;
    ans++;
    ans += k;
    return ans;
}

ll solve2(int n){

    if(n == 1)return 2;
    if(n == 2)return 4;
    ll ans = 0;
    ll k = solve1(n - 2);
    ans += k;
    ans += 2;
    ans += k;
    ans += 2;
    ans += k;
    return ans;
}
int main(){
    int t; scanf("%d", &t);
    while(t--){
        int n; scanf("%d", &n);
        printf("%lld\n", solve2(n));
    }
    return 0;
}

选课时间

problem:

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

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

 先给出AC代码:

// problem :  

#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef pair<int, int> PII;
#define pb push_back

int f[45];
int main(){
    int t; scanf("%d", &t);
    while(t--) {
        int n, k; scanf("%d %d", &n, &k);
        memset(f, 0, sizeof(f));
        f[0] = 1;
        while(k--) {
            int a, b; scanf("%d %d", &a, &b);
            for(int j = n; j >= a; --j)
                for(int m = 1; m <= b && j >= m * a; ++m)
                    f[j] += f[j - a * m];

        } 
        cout << f[n] << endl;
    }   

    return 0;
}

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

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

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

先看看二进制拆分的代码

for(int i = 1; i <= n; ++i){
    int num = min(s[i], V / w[i]);
    for(int k = 1; num; k <<= 1){
        if(k > num)k = num;
        num -= k;
        for(int j = V; j >= k * w[i]; --j)
            f[j] = max(f[j], f[j - k * w[i]] + k * v[i]);
    }
}

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

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

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

for(int i = 1; i <= n; ++i){
    for(int j = V; j >= w[i]; --j){
        int num = min(s[i], j / w[i]);
        for(int k = 1; num; k <<= 1){
            if(k > num)k = num;
            num -= k;
            f[j] = max(f[j], f[j - k * w[i]] + k * v[i]);
        }
    }
}

 这份代码达到的是     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. 列出状态转移方程。
// problem :  

#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef pair<int, int> PII;
#define pb push_back

int l, n, c, t, p[105];
double vr, vt1, vt2;
double f[105];
int main(){
    while(~scanf("%d", &l)) {
        scanf("%d %d %d", &n, &c, &t);
        scanf("%lf %lf %lf", &vr, &vt1, &vt2);
        for(int i = 1; i <= n; ++i)
            scanf("%d", &p[i]);
        p[n + 1] = l;
        memset(f, 0, sizeof(f));
        f[1] = 0;
        for(int i = 2; i <= n + 1; ++i){
            double minn = 1 << 30;
            for(int j = 1; j < i; ++j){
                double time = f[j];
                if(j != 1)
                    time += t;
                int len = p[i] - p[j];
                if(len > c)
                    time += c * 1.0 / vt1 + (len - c) * 1.0 / vt2;
                else
                    time += len * 1.0 / vt1;
                minn = min(minn, time);
            }
            f[i] = minn;
        }
        double res = l * 1.0 / vr;
        if(res < f[n + 1])puts("Good job,rabbit!");
        else puts("What a pity rabbit!");
    }

    return 0;
}

Subset sequence

红色病毒问题

problem: 只能说是毫无头绪

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

// problem :  

#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef pair<int, int> PII;
#define pb push_back
const int p = 100;
// ans=4^(n-1)+2^(n-1)
ll rp(ll now, ll k){
    ll res = 1;
    for(; k; k >>= 1, now *= now, now %= p)
        if(k & 1)
            res *= now, res %= p;
    return res;
}
int t;
int main(){
    while(~scanf("%d", &t) && t) {
        for(int i = 1; i <= t; ++i){
            ll n; scanf("%lld", &n);
            printf("Case %d: %lld\n", i,(rp(4, n - 1) + rp(2, n - 1)) % p);
        }
        putchar(10);
    }
    return 0;
}

一个人的旅行

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

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

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

要对每个pi  做一次dijkstra

// problem :  

#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef pair<int, int> PII;
#define pb push_back
int t, s, d;
struct edge{
    int to, w, next;
}e[100005];
int cnt, head[1005], dis[1005];
inline void makelist(int u, int v, int w){
    e[++cnt].to = v;
    e[cnt].next = head[u];
    e[cnt].w = w;
    head[u] = cnt;
}
struct node{
    int id, _dis;
    bool operator < (const node & a) const {
        return _dis > a._dis;
    }
};
bool vis[1005];
void dijkstra(int _s){
    dis[_s] = 0;
    priority_queue<node> q;
    q.push({_s, dis[_s]});
    while(!q.empty()){
        node k = q.top();
        q.pop();
        int id = k.id;
        if(vis[id])continue;
        vis[id] = true;
        for(int x = head[id]; x; x = e[x].next){
            int to = e[x].to;
            int w = e[x].w;
            if(vis[to])continue;
            if(w + dis[id] < dis[to]){
                dis[to] = w + dis[id];
                q.push({to, dis[to]});
            }
        }
    }
}
void init(){
    memset(vis, false, sizeof(vis));
    memset(dis, 127, sizeof(dis));
    memset(head, 0, sizeof(head));
    cnt = 0;

}
int main(){
    while(~scanf("%d %d %d", &t, &s, &d)){
        init();

        for(int i = 1; i <= t; ++i){
            int x, y, time; scanf("%d %d %d", &x, &y, &time);
            makelist(x, y, time);
            makelist(y, x, time);
        }

        for(int i = 1; i <= s; ++i){
            int x; scanf("%d", &x);
            makelist(1001, x, 0);
        }

        int dd[1005];
        for(int i = 1; i <= d; ++i)
            scanf("%d", &dd[i]);
        
        int ans = 1 << 30;

        dijkstra(1001);
        for(int i = 1; i <= d; ++i)
            ans = min(ans, dis[dd[i]]);
        
        printf("%d\n", ans);
    }

    return 0;
}

小兔的棋盘

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

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

// problem :  

#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef pair<int, int> PII;
#define pb push_back
int n;
ll b[40][40];
ll dfs(int x, int y){
    if(b[x][y])return b[x][y];
    if(x == n || y == n)return 1;
    ll ans = 0;

    ans += dfs(x + 1, y);
    if(y < x)
        ans += dfs(x, y + 1);
    return b[x][y] = ans;
}
int main(){
    int c = 1;
    while(~scanf("%d", &n)){
        memset(b, 0, sizeof(b));
        if(n == -1)break;
        printf("%d %d %lld\n",c++, n, 2 * dfs(0, 0));
    }

    return 0;
}

RPG的错排

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

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

solve:

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

// problem :  

#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef pair<int, int> PII;
#define pb push_back
ll f[30];
ll factorial[30];

void solve(){
    // 阶乘
    factorial[0] = 1;
    for(int i = 1; i <= 25; ++i)
        factorial[i] = factorial[i - 1] * i;

    // 全错排公式
    f[1] = 0, f[2] = 1;
    for(int i = 3; i <= 25; ++i)
        f[i] = (i - 1) * (f[i - 1] + f[i - 2]); 
}
int main(){
    int n;
    solve();
    while(~scanf("%d", &n) && n){
        int k;
        if(n & 1) k = (n+1)/2;
        else k = n / 2;
        ll ans = 0;

        for(int m = k; m <= n - 2; ++m){
            // ans += factorial(n)/factorial(n - m)/factorial(m) * f[n - m];
            ll c = 1;
            for(int i = m + 1; i <= n; ++i)
                c *= i;
            // c = factorial(n) / factorial(m)  --->  (m + 1) ... n 各位相乘
            ans += c / factorial[n - m] * f[n - m];
        }
        ans++;
        printf("%lld\n", ans);
    }
    return 0;
}

Coin Change

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

 没看到题目wa了的代码:

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

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

// problem :  

#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef pair<int, int> PII;
#define pb push_back
int type[5] = {1, 5, 10, 25, 50};
int f[255][105];
void solve(){
    f[0][0] = 1;
    for(int i = 0; i < 5; ++i)
        for(int j = 1; j <= 100; ++j)
            for(int k = type[i]; k <= 250; ++k)
                f[k][j] += f[k - type[i]][j - 1];
}

int main(){
    int n;
    solve();
    while(~scanf("%d", &n)){
        int ans = 0;
        for(int i = 0; i <= 100; ++i)
            ans += f[n][i];
        printf("%d\n", ans);
    }

    return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xingxg.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值