CSP-CCF认证考试(第一次)总结,题解

小结:

题目整体难度不大,前四题还好写,第五题hard难度的dp,思路确实想不到,写了三个小时,第三题模拟题不知道stringstream,写得很吃力,最后放弃了3,5题,得分300。有点菜继续加油。

题解:(一,二题简单无思路,三,五题代码参考自acwing)

题一:相反数

有 N𝑁 个非零且各不相同的整数。

请你编一个程序求出它们中有多少对相反数(a 和 −a 为一对相反数)。

输入格式

第一行包含一个正整数 N𝑁。

第二行为 N 个用单个空格隔开的非零整数,每个数的绝对值不超过 1000,保证这些整数各不相同。

输出格式

只输出一个整数,即这 N 个数中包含多少对相反数。

数据范围

1≤N≤500

代码:

​
#include<bits/stdc++.h>
using namespace std;
const int N=1001;
int g[N];
int ans[N];
int n;
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>g[i];
       if(g[i]>0){
            ans[g[i]]++;
        }else if(g[i]<0){
            ans[-g[i]]++;
        }
    }
    int res=0;
    for(int i=1;i<=1000;i++){
        if(ans[i]>1){
            res++;
        }
    }
    cout<<res;
}

​

题二: 窗口

在某图形操作系统中,有 N 个窗口,每个窗口都是一个两边与坐标轴分别平行的矩形区域。

窗口的边界上的点也属于该窗口。

窗口之间有层次的区别,在多于一个窗口重叠的区域里,只会显示位于顶层的窗口里的内容。

当你点击屏幕上一个点的时候,你就选择了处于被点击位置的最顶层窗口,并且这个窗口就会被移到所有窗口的最顶层,而剩余的窗口的层次顺序不变。

如果你点击的位置不属于任何窗口,则系统会忽略你这次点击。

现在我们希望你写一个程序模拟点击窗口的过程。

输入格式

输入的第一行有两个正整数,即 N和 M。

接下来 N 行按照从最下层到最顶层的顺序给出 N 个窗口的位置。

每行包含四个非负整数 x1,y1,x2,y2,表示该窗口的一对顶点坐标分别为 (x1,y1) 和 (x2,y2)。保证 x1<x2,y1<y2。

接下来 M 行每行包含两个非负整数 x,y表示一次鼠标点击的坐标。

题目中涉及到的所有点和矩形的顶点的 x,y坐标分别不超过 2559 和 1439。

输出格式

输出包括 M 行,每一行表示一次鼠标点击的结果。

如果该次鼠标点击选择了一个窗口,则输出这个窗口的编号(窗口按照输入中的顺序从 1 编号到 N)。

如果没有,则输出 IGNORED

数据范围

1≤N,M≤10

代码:

#include<bits/stdc++.h>
using namespace std;
struct G{
    int th;
    int x1,x2,y1,y2;
}g[20];
int n,m;
void solve()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        int a,b,c,d;
        cin>>a>>b>>c>>d;
        g[i].th=i;
        g[i].x1=a,g[i].y1=b,g[i].x2=c,g[i].y2=d;
    }
    for(int i=1;i<=m;i++){
        G tex;
        bool flag=0;
        int x,y;
        cin>>x>>y;
        for(int j=n;j>0;j--){
            if(x>=g[j].x1&&x<=g[j].x2&&y>=g[j].y1&&y<=g[j].y2){
                flag=1;
                cout<<g[j].th<<endl;
                tex=g[j];
                for(int k=j;k<n;k++){
                    g[k]=g[k+1];
                }
                g[n]=tex;
                break;
            }
        }
        if(!flag){
            cout<<"IGNORED"<<endl;
        }
    }
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int t=1;
    while(t--){
        solve();
    }
    return 0;
}

题三: 命令行选项

这个字符串由若干小写字母和冒号组成,其中的每个小写字母表示一个该程序接受的选项。

如果该小写字母后面紧跟了一个冒号,它就表示一个带参数的选项,否则则为不带参数的选项。

例如,ab:m: 表示该程序接受三种选项,即 -a(不带参数),-b(带参数),以及 -m(带参数)。

命令行工具的作者准备了若干条命令行用以测试你的程序。

对于每个命令行,你的工具应当一直向后分析。

当你的工具遇到某个字符串既不是合法的选项,又不是某个合法选项的参数时,分析就停止。

命令行剩余的未分析部分不构成该命令的选项,因此你的程序应当忽略它们。

输入格式

输入的第一行是一个格式字符串,它至少包含一个字符,且长度不超过 52。

格式字符串只包含小写字母和冒号,保证每个小写字母至多出现一次,不会有两个相邻的冒号,也不会以冒号开头。

输入的第二行是一个正整数 N,表示你需要处理的命令行的个数。

接下来有 N行,每行是一个待处理的命令行,它包括不超过 256 个字符。该命令行一定是若干个由单个空格分隔的字符串构成,每个字符串里只包含小写字母,数字和减号。

输出格式

输出有 N 行。其中第 i 行以 Case i: 开始,然后应当有恰好一个空格,然后应当按照字母升序输出该命令行中用到的所有选项的名称,对于带参数的选项,在输出它的名称之后还要输出它的参数。

如果一个选项在命令行中出现了多次,只输出一次。

如果一个带参数的选项在命令行中出现了多次,只输出最后一次出现时所带的参数。

数据范围

1≤N≤20,
对于每组数据,所有命令行工具的名字一定相同,且由小写字母构成。

思路:

模拟题,没有思维难度,主要是细节,用到了stringstream的方法读取字符串。

采用了两个bool数组记录不同方法。

代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <sstream>

using namespace std;

const int N = 30;

bool st1[N], st2[N]; // st1 存放的是无参数选项  st2  存放的是有参数选项
string ans[N];  // 该字母后存放的参数   
int n;

int main()
{
    string str;
    cin >> str;
    // 处理第一行的数据  进行标注(之后存在这个点才会输出)
    for (int i = 0; i < str.size(); i ++)
    {
        if (str[i+1] == ':' && i + 1 < str.size()) {
            st2[str[i] - 'a'] = true;
            i ++; // 注意
        }
        else st1[str[i] - 'a'] = true;
    }
    cin >> n;
    getchar(); // 除去输入n后边的换行  注意

    for (int C = 1; C <= n; C ++) 
    {
        printf("Case %d:", C);
        //注意
        getline(cin,str);
        stringstream ssin(str);
        vector<string> ops;
        while (ssin >> str) ops.push_back(str);

        for (int i = 0; i < 26; i ++) ans[i].clear();//注意

        for (int i = 1; i < ops.size(); i ++) // 从1开始做  ls不用管
        {
            //不满足条件break
            if (ops[i][0] != '-' || ops[i][1] < 'a' || ops[i][1] > 'z' || ops[i].size() != 2) break;
            int k = ops[i][1] - 'a';
            if (st1[k]) ans[k] = '/';
            else if(st2[k] && i + 1 < ops.size()) ans[k] = ops[i+1], i ++; // 注意
            else break;
        }

        for (int i = 0; i < 26; i ++)
        {
            if (ans[i].size())
            {
                cout << " -" << char (i + 'a'); //注意
                if (st2[i]) cout << " " << ans[i];
            }
        }

        cout << endl;
    }
    return 0;
}

 题四:无线网络

目前在一个很大的平面房间里有 n个无线路由器,每个无线路由器都固定在某个点上。

任何两个无线路由器只要距离不超过 r 就能互相建立网络连接。

除此以外,另有 m 个可以摆放无线路由器的位置。

你可以在这些位置中选择至多 k 个增设新的路由器。

你的目标是使得第 1 个路由器和第 2 个路由器之间的网络连接经过尽量少的中转路由器。

请问在最优方案下中转路由器的最少个数是多少?

输入格式

第一行包含四个正整数 n,m,k,r。

接下来 n行,每行包含两个整数 xi 和 yi,表示一个已经放置好的无线路由器在 (xi,yi) 点处。输入数据保证第 1 和第 2 个路由器在仅有这 n 个路由器的情况下已经可以互相连接(经过一系列的中转路由器)。

接下来 m 行,每行包含两个整数 xi 和 yi,表示 (xi,yi) 点处可以增设一个路由器。

输入中所有的坐标的绝对值不超过 1e8,保证输入中的坐标各不相同。

输出格式

输出只有一个数,即在指定的位置中增设 k 个路由器后,从第 1个路由器到第 2 个路由器最少经过的中转路由器的个数。

思路:

采用bfs,但是不能用二维数组存点,会超内存。采用邻接表的方式存点,将距离不超过r的点之间建立无向边。采用拆点的思路用二维数组dis[i][j]存值表示i点在过j个新路由器的最短距离。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 210, M = N * N;
typedef pair<int, int> PII;
int h[N], e[M], ne[M], idx;
struct P {
    long long x, y;
}p[N];
int dis[N][N];
int n, m, k, r;
bool st[N][N];
int cnt;
void add(int a, int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void bfs()
{
    memset(dis, 0x3f, sizeof(dis));
    queue<PII> q;
    q.push({ 1,0 });
    dis[1][0] = 0;
    while (q.size()) {
        auto t = q.front();
        int x = t.first, y = t.second;
        q.pop();
        if (st[x][y]) {
            continue;
        }
        st[x][y] = true;
        for (int i = h[x];i != -1;i = ne[i]) {
            y = t.second;
            int j = e[i];
            if (j > n) {
                y++;
                if (y <= k) {
                    if (dis[j][y] > dis[x][y - 1] + 1) {
                        dis[j][y] = dis[x][y - 1] + 1;
                        q.push({ j,y });
                    }
                }
            }
            else {
                if (dis[j][y] > dis[x][y] + 1) {
                    dis[j][y] = dis[x][y] + 1;
                    q.push({ j,y });
                }
            }

        }
    }
}
void solve()
{
    memset(h, -1, sizeof(h));
    cin >> n >> m >> k >> r;
    for (int i = 1;i <= n;i++) {
        cin >> p[i].x >> p[i].y;
    }
    for (int i = 1;i <= m;i++) {
        cin >> p[n + i].x >> p[n + i].y;
    }
    for (int i = 1;i <= n + m;i++) {
        for (int j = i + 1;j <= n + m;j++) {
            long long o = (p[i].x - p[j].x) * (p[i].x - p[j].x) + (p[i].y - p[j].y) * (p[i].y - p[j].y);
            if (o <= (long long)r * r) {
                add(i, j);
                add(j, i);
            }
        }
    }
    bfs();
    int ans = 0x3f3f3f3f;
    for (int i = 1;i <= k;i++) {
        ans = min(ans, dis[2][i]);
    }
    cout << ans-1;
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int t = 1;
    while (t--) {
        solve();
    }
    return 0;
}

题五: 任务调度

有若干个任务需要在一台机器上运行。

它们之间没有依赖关系,因此可以被按照任意顺序执行。

该机器有两个 CPU 和一个 GPU。

对于每个任务,你可以为它分配不同的硬件资源:

  1. 在单个 CPU 上运行。
  2. 在两个 CPU 上同时运行。
  3. 在单个 CPU 和 GPU 上同时运行。
  4. 在两个 CPU 和 GPU 上同时运行。

一个任务开始执行以后,将会独占它所用到的所有硬件资源,不得中断,直到执行结束为止。

第 i𝑖 个任务用单个 CPU,两个 CPU,单个 CPU 加 GPU,两个 CPU 加 GPU 运行所消耗的时间分别为 ai,bi,ci 和 di。

现在需要你计算出至少需要花多少时间可以把所有给定的任务完成。

输入格式

输入的第一行只有一个正整数 n,是总共需要执行的任务个数。

接下来的 n 行每行有四个正整数 ai,bi,ci,di,以空格隔开。

输出格式

输出只有一个整数,即完成给定的所有任务所需的最少时间。

数据范围

1≤n≤40,
1≤ai,bi,ci,di≤10

思路:

首先将四个调度可以分为三种,可以看到每种调度都需要CPU,所以,可以将调度2、4合并,记为新的调度3,
且可以放在其它所有任务之后执行,不会影响整体时间(这种占据所有资源,所以直接将占用时间累加到总时间上即可),
而调度1:占用一个CPU,调度2:占用一个CPU和一个GPU,调度1和调度1,调度1和调度2均不会冲突,
调度总时间直接加上调度的最大时间即可,但是调度2和调度2会由于争夺GPU而冲突,
假设使用CPU1总时间为i,CPU2总时间为j,GPU总时间为k
调度的总时间的最小值依旧为max(i,j,k),两个CPU错峰使用GPU即可,不会对最少总时间有影响
尝试使用DP求解
f[u][i][j][k]表示前u个任务,CPU1使用总时间i,CPU2使用总时间j,GPU使用总时间k时,
调度3占用时间的最小值,状态转移可以通过任务u的调度选择来完成

代码:

#include <bits/stdc++.h>

using namespace std;

const int N = 50, M = 210, INF = 0x3f3f3f3f;

int n;
int f[2][M][M][M];
int c[N][3];

int main()
{
    cin >> n;

    int m = 0, m2 = 0;
    for (int i = 1; i <= n; i ++)
    {
        int x, y, z, t;
        cin >> x >> y >> z >> t;
        c[i][0] = x, c[i][1] = z, c[i][2] = min(y, t);
        m += x;
        if (i % 2) m2 += x;
    }

    m = max(m2, m - m2);

    memset(f, 0x3f, sizeof f);
    f[0][0][0][0] = 0;
    for (int u = 1; u <= n; u ++)
        for (int i = 0; i <= m; i ++)
            for (int j = i; j <= m; j ++)
                for (int k = 0; k <= j; k ++)  //k<=j (GPU不能单独使用)
                {
                    int &v = f[u & 1][i][j][k];
                    int x = c[u][0], y = c[u][1], z = c[u][2], t = u - 1 & 1;
                    v = f[t][i][j][k] + z;  //模式3
                    if (i >= x) v = min(v, f[t][min(i - x, j)][max(i - x, j)][k]);  //模式1
                    if (j >= x) v = min(v, f[t][min(j - x, i)][max(j - x, i)][k]);  //模式1
                    if (i >= y && k >= y) v = min(v, f[t][i - y][j][k - y]);  //模式2
                    if (j >= y && k >= y) v = min(v, f[t][i][j - y][k - y]);  //模式2
                }

    int res = INF;
    for (int i = 0; i <= m; i ++)
        for (int j = i; j <= m; j ++)
            for (int k = 0; k <= j; k ++)
                res = min(res, f[n & 1][i][j][k] + max(max(i, j), k));

    cout << res;

    return 0;
}

  • 20
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值