第十二次CCF计算机软件能力认证

第一题:最小差值

给定 n 个数,请找出其中相差(差的绝对值)最小的两个数,输出它们的差值的绝对值。

输入格式

输入第一行包含一个整数 n。

第二行包含 n 个正整数,相邻整数之间使用一个空格分隔。

输出格式

输出一个整数,表示答案。

数据范围

对于所有评测用例,2≤n≤1000,每个给定的整数都是不超过 10000 的正整数。

输入样例1:
5
1 5 4 8 20
输出样例1:
1
样例1解释

相差最小的两个数是 5 和 4,它们之间的差值是 1。

输入样例2:
5
9 3 6 1 3
输出样例2:
0
样例2解释

有两个相同的数 3,它们之间的差值是 0。

 解题思路:

双重循环寻找即可

#include<iostream>

using namespace std;

const int N = 1010;
int n;
int a[N];

int main()
{
    cin >> n;
    
    for(int i = 0;i < n;i ++)
        cin >> a[i];
    
    int res = 0x3f3f3f3f;
    
    for(int i = 0;i < n;i ++)
        for(int j = i + 1;j < n;j ++)
            res = min(res , abs(a[i] - a[j]));
    
    cout << res << endl;
    return 0;
}

第二题:游戏

有 n 个小朋友围成一圈玩游戏,小朋友从 1 至 n 编号,2 号小朋友坐在 1 号小朋友的顺时针方向,3 号小朋友坐在 2 号小朋友的顺时针方向,……,1 号小朋友坐在 n 号小朋友的顺时针方向。

游戏开始,从 1 号小朋友开始顺时针报数,接下来每个小朋友的报数是上一个小朋友报的数加 1。

若一个小朋友报的数为 k 的倍数或其末位数(即数的个位)为 k,则该小朋友被淘汰出局,不再参加以后的报数。

当游戏中只剩下一个小朋友时,该小朋友获胜。

例如,当 n=5,k=2 时:

  • 1 号小朋友报数 1;
  • 2 号小朋友报数 2 淘汰;
  • 3 号小朋友报数 3;
  • 4 号小朋友报数 4 淘汰;
  • 5 号小朋友报数 5;
  • 1 号小朋友报数 6 淘汰;
  • 3 号小朋友报数 7;
  • 5 号小朋友报数 8 淘汰;
  • 3 号小朋友获胜。
  • n 和 k,请问最后获胜的小朋友编号为多少?
输入格式

输入一行,包括两个整数 n 和 k,意义如题目所述。

输出格式

输出一行,包含一个整数,表示获胜的小朋友编号。

数据范围

对于所有评测用例,1≤n≤1000,1≤k≤9。

输入样例1:
5 2
输出样例1:
3
输入样例2:
7 3
输出样例2:
4
解题思路:

像是约瑟夫环,但是对于简单题直接模拟就行

#include<iostream>
#include<cstring>

using namespace std;

const int N = 1010;
int n , k;
bool st[N];

int main()
{
    cin >> n >> k;
    
    memset(st , 0 , sizeof st);
    int now = 1;
    int cnt = 0 , last = n;
    
    while(last != 1)
    {
        if(now == n + 1) now = 1;
        
        if(!st[now])
        {
            cnt ++;
            if(cnt % k == 0 || cnt % 10 == k) 
            {
                st[now] = true;
                last --;
            }
        }
        now ++;
    }
    
    for(int i = 1;i <= n;i ++)
        if(!st[i]) cout << i << endl;
    return 0;
}

第三题:Crontab

超级大模拟

// 总体思路:遍历每一个时间点;对每一个时间点遍历所有可能执行的命令
// Task List
// 1. 时间的存储
    // 1.1 能够实现next: next()
    // 1.2 能够打印: to_string()
// 2. 任务的存储
    // 2.1 所有可行时间: bool 数组 + check()
    // 2.2 可读取特定格式的输入: work()


# include <iostream>
# include <unordered_map>
using namespace std;

int months[13] = {
    0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};

struct Timer {
    int year, month, day, week, hour, minute;
    // 读入(初始化)
    Timer(string str) {
        sscanf(str.c_str(), "%04d%02d%02d%02d%02d", &year, &month, &day, &hour, &minute);
    }

    // 格式化打印
    string to_string() {
        char str[20];
        sprintf(str, "%04d%02d%02d%02d%02d", year, month, day, hour, minute);
        return str;
    }

    bool operator< (const Timer& t) const {
        if (year != t.year) return year < t.year;
        if (month != t.month) return month < t.month;
        if (day != t.day) return day < t.day;
        if (hour != t.hour) return hour < t.hour;
        return minute < t.minute;
    }

    int is_leap() {
        if (year % 400 == 0 || year % 100 != 0 && year % 4 == 0) return 1;
        return 0;
    }

    int get_day() {
        if (month == 2) return months[month] + is_leap();
        return months[month];
    }
    // next
    void next() {
        if ( ++ minute == 60) {
            minute = 0;
            if ( ++ hour == 24) {
                hour = 0;
                week = (week + 1) % 7;
                if ( ++ day > get_day()) {
                    day = 1;
                    if ( ++ month == 13) {
                        month = 1;
                        year ++;
                    }
                }
            }
        }
    }

};

struct Task {
    bool minutes[60], hours[24], day_of_month[32], month[13], day_of_week[7];
    string name;
    bool check(Timer& t) {
        return minutes[t.minute] && hours[t.hour] && day_of_month[t.day] &&
            month[t.month] && day_of_week[t.week];
    }
}task[20];

unordered_map<string, int> nums;
void init() {
    string keys[] = {
        "jan", "feb", "mar", "apr", "may", "jun",
        "jul", "aug", "sep", "oct", "nov", "dec",
        "sun", "mon", "tue", "wed", "thu", "fri",
        "sat"
    };
    int values[] = {
        1, 2, 3, 4, 5, 6,
        7, 8, 9, 10, 11, 12,
        0, 1, 2, 3, 4, 5,
        6
    };
    for (int i = 0; i < 19; i ++ )
        nums[keys[i]] = values[i];
}


int get(string str) {
    if (str[0] >= '0' && str[0] <= '9') return stoi(str);
    string s;
    for (auto c: str) s += tolower(c);
    return nums[s];
}

void work(string str, bool st[], int len) {
    if (str.find('*') != -1) {
        for (int i = 0; i < len; i ++ ) 
            st[i] = true;
    }
    else {
        // 双指针,以','为分界单独处理每一部分
        for (int i = 0; i < str.size(); i ++ ) {
            if (str[i] == ',') continue;
            int j = i + 1;
            while (j < str.size() && str[j] != ',') j ++;
            string s = str.substr(i, j - i);
            i = j; // 不知道为什么不能写成i = j + 1

            // 处理单独部分
            int k = s.find('-');
            if (k != -1) {
                int l = get(s.substr(0, k)), r = get(s.substr(k + 1));
                for (int u = l; u <= r; u ++ ) st[u] = true;
            }
            else st[get(s)] = true;
        }
    }
}


int main() {
    init();
    int n;
    string start, end;
    cin >> n >> start >> end;
    for (int i = 0; i < n; i ++ ) {
        string minutes, hours, day_of_month, month, day_of_week, name;
        cin >> minutes >> hours >> day_of_month >> month >> day_of_week >> name;
        work(minutes, task[i].minutes, 60);
        work(hours, task[i].hours, 24);
        work(day_of_month, task[i].day_of_month, 32);
        work(month, task[i].month, 13);
        work(day_of_week, task[i].day_of_week, 7);
        task[i].name = name;
    }

    Timer t("197001010000"), S(start), E(end);
    t.week = 4;

    while (t < E) {
        if (!(t < S)) {
            for (int i = 0; i < n; i ++ ) 
                if (task[i].check(t))
                    cout << t.to_string() << ' ' << task[i].name << endl;
        }
        t.next();
    }
    return 0;
}

第四题:行车路线

小明和小芳出去乡村玩,小明负责开车,小芳来导航。

小芳将可能的道路分为大道和小道。

大道比较好走,每走 1 公里小明会增加 1 的疲劳度。

小道不好走,如果连续走小道,小明的疲劳值会快速增加,连续走 s 公里小明会增加 s2 的疲劳度。

例如:有 5 个路口,1 号路口到 2 号路口为小道,2 号路口到 3 号路口为小道,3 号路口到 4 号路口为大道,4 号路口到 5 号路口为小道,相邻路口之间的距离都是 2 公里。

如果小明从 1 号路口到 5 号路口,则总疲劳值为 (2+2)^2+2+2^2=16+2+4=22。

现在小芳拿到了地图,请帮助她规划一个开车的路线,使得按这个路线开车小明的疲劳度最小。

输入格式

输入的第一行包含两个整数 n,m,分别表示路口的数量和道路的数量。路口由 1 至 n 编号,小明需要开车从 1 号路口到 n 号路口。

接下来 m 行描述道路,每行包含四个整数 t,a,b,c,表示一条类型为 t,连接 a 与 b 两个路口,长度为 c 公里的双向道路。其中 t 为 00 表示大道,t 为 11 表示小道。

保证 1 号路口和 n 号路口是连通的。

输出格式

输出一个整数,表示最优路线下小明的疲劳度。

数据范围

对于 30% 的评测用例,1≤n≤8,1≤m≤10;
对于另外 20% 的评测用例,不存在小道;
对于另外 20% 的评测用例,所有的小道不相交;
对于所有评测用例,1≤n≤500,1≤m≤105,1≤a,b≤n,t 是 0 或 1,c≤1e5。
保证答案不超过 1e6。

输入样例:
6 7
1 1 2 3
1 2 3 2
0 1 3 30
0 3 4 20
0 4 5 30
1 3 5 6
1 5 6 1
输出样例:
76
样例解释

从 1 走小道到 2,再走小道到 3,疲劳度为 5^2=25;然后从 3 走大道经过 4 到达 5,疲劳度为 20+30=50;最后从 5 走小道到 6,疲劳度为 1。

总共为 76。

解题思路: 

题目给定所有答案不超过1e6,其实也就保证了连续小路的长度不超过1000(1000的平方就是1e6)

若要更新k点

大路:dist[k][0] = dist[i][j] + w

小路:dist[k][j + w] = dist[i][j] - j ^ 2 + (j + w) ^ 2 考虑连续小路的平方

#include<iostream>
#include<cstring>
#include<vector>
#include<queue>
#include<algorithm>

using namespace std;

const int N = 5e5 + 10 , M = 510 , INF = 0x3f3f3f3f3f;
int n , m;
int h[N] , ne[N] , w[N] , e[N] , idx = 0;
int f[N]; // 大路和小路
int dist[M][1010]; // 从1到点i的最短距离,第二维j表示1到i点这条路径上最后那段(连接i)的小路的长度
bool st[M][1010];

struct node
{
    int x , y , v;
    bool operator < (const node&t) const
    {
        return v > t.v;
    }
};

void add(int t , int a , int b , int c)
{
    e[idx] = b , f[idx] = t , w[idx] = c , ne[idx] = h[a] , h[a] = idx ++;
}

void dij()
{
    memset(st , 0 , sizeof st);
    memset(dist , 0x3f , sizeof dist);
    
    priority_queue<node>q;
    
    q.push({1 , 0 , 0});
    dist[1][0] = 0;
    while(!q.empty())
    {
        auto t = q.top();
        q.pop();
        
        if(st[t.x][t.y]) continue;
        st[t.x][t.y] = true;
        
        for(int i = h[t.x];~i;i = ne[i])
        {
            int j = e[i] , y = t.y;
            if(f[i]) // 小路
            {
                y += w[i];// 小路更新
                if(y <= 1000)
                {
                    if(dist[j][y] > t.v - t.y * t.y + y * y)
                    {
                        dist[j][y] = t.v - t.y * t.y + y * y;
                        if(dist[j][y] <= INF) q.push({j , y , dist[j][y]});
                    }
                }
            }
            else
            {
                if(dist[j][0] > t.v + w[i])
                {
                    dist[j][0] = t.v + w[i];
                    if(dist[j][0] <= INF) q.push({j , 0 , dist[j][0]});
                }
            }
        }
    }
}

int main()
{
    memset(h , -1 , sizeof h);
    cin >> n >> m;
    while(m --)
    {
        int t , a , b , c;
        cin >> t >> a >> b >> c;
        add(t , a , b , c) , add(t , b , a , c);
    }
    
    dij();
    int res = INF;
    for(int i = 0;i <= 1000;i ++) res = min(res , dist[n][i]);
    
    cout << res << endl;
    return 0;
}

第五题:商路

dp+图论+线段树+dfs

 

#include <string.h>
#include <iostream>
#include <vector>
using namespace std;
typedef long long ll;
const ll mod = 1e18;
const int N = 1e5 + 5;

struct Edge
{
    int t, s, ne;
} e[N];
int h[N], idx1;
void add(int u, int v, int s) { e[++idx1] = {v, s, h[u]}, h[u] = idx1; }

ll v[N], f[N];
ll d[N], dp[N];
int sz[N], ind[N], idx2;
void dfs1(int from, ll s)
{
    // 第一趟 dfs,求出 dfs 序、到根节点的距离、子树大小
    ind[from] = ++idx2, d[idx2] = s, sz[from] = 1;
    for (int i = h[from]; i; i = e[i].ne)
        dfs1(e[i].t, s + e[i].s), sz[from] += sz[e[i].t];
}

#define sq(x) ((x) * (x))            // 求平方 square,记得 x 要加括号:(x)
#define ff(x) (dp[x] - sq(d[x]))     // 凸包点的纵坐标值
#define dy(x1, x2) (ff(x1) - ff(x2)) // dy/dx 即斜率
#define dx(x1, x2) (d[x1] - d[x2])   // 凸包点的横坐标 d[x],用不上就不单独定义了
struct Node
{
    bool vis;
    vector<int> q; // 维护的凸包
    void build()
    { // 构造凸包,就地算法,一趟遍历即可
        int r = 0;
        for (int &i : q)
        {
            while (r > 1 &&
                   (double)dy(q[r - 1], q[r - 2]) * dx(i, q[r - 1]) <=
                       (double)dy(i, q[r - 1]) * dx(q[r - 1], q[r - 2]))
                r--;
            q[r++] = i;
        }
        q.resize(r);
    }
    ll find(int i)
    { // 二分搜索,传入的 i 是原序号,ind[i] 为 dfs 序
        if (!vis)
            vis = 1, build();

        ll k = -2 * (f[i] + d[ind[i]]); // 斜率,记得取负
        int l = -1, r = q.size() - 1;   // 如果只有一个点,直接取 r 即可
        while (r - l > 1)
        { // 至少有两个点的时候,二分搜索
            int mid = l + r >> 1;
            if ((double)dy(q[mid + 1], q[mid]) <=
                (double)k * dx(q[mid + 1], q[mid]))
                r = mid; // r 一定是可取的
            else
                l = mid; // l 一定是不取的
        }

        return dp[q[r]] + v[i] - sq(f[i] + d[ind[i]] - d[q[r]]);
    }
} tr[N << 2];

#define cur tr[x]          // 当前节点
#define lch tr[x << 1]     // 左孩子节点
#define rch tr[x << 1 | 1] // 右孩子节点
#define mid (l + r >> 1)
void merge(int x)
{ // 归并排序的 merge 算法
    cur.q.resize(lch.q.size() + rch.q.size());
    int i = 0, j = 0, k = 0;
    while (i < lch.q.size() || j < rch.q.size())
        if (i == lch.q.size())
            cur.q[k++] = rch.q[j++];
        else if (j == rch.q.size())
            cur.q[k++] = lch.q[i++];
        else if (d[lch.q[i]] <= d[rch.q[j]])
            cur.q[k++] = lch.q[i++];
        else
            cur.q[k++] = rch.q[j++];
}
void build(int x, int l, int r)
{ // 构造线段树
    cur.vis = 0;
    if (l == r)
        cur.q.resize(1, l);
    else
        build(x << 1, l, mid), build(x << 1 | 1, mid + 1, r), merge(x);
}

int L, R, n, T; // [L, R] 为查询的区间
ll query(int x, int l, int r, int i)
{ // [l, r] 为当前节点维护的区间
    if (l >= L && r <= R)
        return cur.find(i);
    else
        return max(mid >= L ? query(x << 1, l, mid, i) : 0,
                   mid < R ? query(x << 1 | 1, mid + 1, r, i) : 0);
}
void dfs2(int from)
{
    if (!h[from])
        return;
    // 加引用,h 数组用完即弃
    for (int &i = h[from]; i; i = e[i].ne)
        dfs2(e[i].t);
    // from 为原标号,ind[from] 为 dfs 序
    L = ind[from] + 1, R = ind[from] + sz[from] - 1;
    dp[ind[from]] = query(1, 1, n, from);
}
int main()
{
    scanf("%d", &T);
    while (T--)
    {
        idx1 = idx2 = 0;
        scanf("%d", &n);
        for (int i = 1, u, s; i <= n; i++)
            scanf("%d%d%lld%lld", &u, &s, v + i, f + i), add(u, i, s);
        dfs1(1, 0), build(1, 1, n), dfs2(1);
        ll ans = 0; // 斜率优化不能对 dp 值取模,否则维护不了凸包
        for (int i = 1; i <= n; i++)
            ans = (ans + dp[i]) % mod, dp[i] = 0;
        printf("%lld\n", ans);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值