2018年全国多校算法寒假训练营练习比赛(第一场)题解

A:贪心+暴力

 

链接:大吉大利,今晚吃鸡——枪械篇

 

思路:我用了一个map使配件种类对应一个最大威力的配件。然后暴力判断每把枪都装上最优的配件以后的威力,求一个极大值即可。

 

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
int n, m, k;
double p[MAXN];//枪的威力
set<int> s[MAXN];//配件种类
map<int, double> M;//最优配件
int main()
{
    while (~scanf("%d%d", &n, &m))
    {
        M.clear();
        for (int i = 0; i < n; i++)
        {
            s[i].clear();
            scanf("%lf%d", &p[i], &k);
            while (k--)
            {
                int t; scanf("%d", &t);
                s[i].insert(t);
            }
        }
        while (m--)
        {
            int q; double b;
            scanf ("%d%lf", &q, &b);
            if (M.count(q))
            {
                M[q] = max(M[q], b);//保存q配件的最大威力
            }
            else M[q] = b;
        }
        double MAX = -1.0;
        for (int i = 0; i < n; i++)
        {
            double t = 1.0;
            for (auto j: s[i])
            {
                t = t + M[j];
            }
            MAX = max(MAX, t * p[i]);
        }
        printf("%.4f\n", MAX);
    }
    return 0;
}
/*
3 6
120 3 1 2 3
100 4 1 2 3 4
110 3 2 3 4
1 0.12
2 0.23
2 0.26
4 0.57
3 0.35
5 0.41
*/

B:vector模拟

 

 

链接:最强的决斗者一切都是必然的!


 

思路:直接模拟就好了,题意比较难读,主要需要看样例。

 

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
int n;
struct Node
{
    int s, t, x;
}a[MAXN];
int main()
{
    while (~scanf("%d", &n))
    {
        for (int i = 1; i <= n; i++)
        {
            scanf("%d%d", &a[i].s, &a[i].t);
            if (a[i].t == 1 || a[i].t == 2) scanf("%d", &a[i].x);
        }
        long long ans = 0;//伤害
        vector<Node> v;//连锁序列,下标表示连锁数
        for (int i = 1; i <= n; i++)
        {
            if (a[i].s >= a[i - 1].s)//连锁
            {
                v.push_back(a[i]);//放入vector
            }
            else//连锁结束
            {
                //计算此次连锁的卡牌的伤害
                for (int j = v.size() - 1; j >= 0; j--)
                {
                    if (v[j].t == 3) break;//所有卡牌无效,则直接跳出
                    if (v[j].t == 4) { j--; continue; }//上一张卡牌无效
                    if (v[j].t == 1) ans = ans + v[j].x;//伤害为x
                    if (v[j].t == 2) ans = ans + (j + 1) * v[j].x;//伤害为x*连锁数
                }
                v.clear();//清空连锁卡牌
                v.push_back(a[i]);//新的连锁
            }
        }
        //计算最后一次连锁伤害
        for (int j = v.size() - 1; j >= 0; j--)
        {
            if (v[j].t == 3) break;
            if (v[j].t == 4) { j--; continue; }
            if (v[j].t == 1) ans = ans + v[j].x;
            if (v[j].t == 2) ans = ans + (j + 1) * v[j].x;
        }
        printf("%lld\n", ans);
    }
    return 0;
}
/*
9
1 1 300
2 2 400
2 3
2 2 500
1 1 1000
3 4
2 1 600
3 3
3 4
*/

C:模拟

 

链接:六子冲 

 

思路:共有八种情况,模拟一下即可。情况比较多,别错了就好。实在是没想到其他好的办法,感觉自己的方法太蠢了。

 

#include<bits/stdc++.h>
using namespace std;
int n, a[10][10], b[10], dir[5][2] = {{0, 0}, {-1, 0}, {1, 0}, {0, -1}, {0, 1}};
void init()
{
    memset(a, 0, sizeof(a));
    //黑棋
    a[3][1] = 1; a[4][1] = 2; a[4][2] = 3; a[4][3] = 4; a[4][4] = 5; a[3][4] = 6;
    //白棋
    a[2][4] = 7; a[1][4] = 8; a[1][3] = 9; a[1][2] = 10; a[1][1] = 11; a[2][1] = 12;
}
void Find(int q, int& row, int& col)//求得q棋子的位置
{
    for (int i = 1; i <= 4; i++)
    {
        for (int j = 1; j <= 4; j++)
        {
            if (a[i][j] == q)
            {
                row = i; col = j;
            }
        }
    }
}
int judge(int num)
{
    if (1 <= num && num <= 6) return 1;//黑
    else if(7 <= num && num <= 12) return -1;//白
    return 0;//空
}
void work(int row, int col)
{
    for(int i = 1; i <= 4; i++) b[i] = judge(a[row][i]);//消除行
    if((b[2]==1 && b[3]==1 && b[1]==-1 && judge(a[row][col])==1 || b[2]==-1 && b[3]==-1 && b[1]==1 && judge(a[row][col])==-1) && b[4]==0)
        a[row][1] = 0;
    if((b[3]==1 && b[4]==1 && b[2]==-1 && judge(a[row][col])==1 || b[3]==-1 && b[4]==-1 && b[2]==1 && judge(a[row][col])==-1) && b[1]==0)
        a[row][2] = 0;
     if((b[1]==1 && b[2]==1 && b[3]==-1 && judge(a[row][col])==1 || b[1]==-1 && b[2]==-1 && b[3]==1 && judge(a[row][col])==-1) && b[4]==0)
        a[row][3] = 0;
    if((b[2]==1 && b[3]==1 && b[4]==-1 && judge(a[row][col])==1 || b[2]==-1 && b[3]==-1 && b[4]==1 && judge(a[row][col])==-1) && b[1]==0)
        a[row][4] = 0;

    for(int i = 1; i <= 4; i++)  b[i] = judge(a[i][col]);//消除列
    if((b[2]==1 && b[3]==1 && b[1]==-1 && judge(a[row][col])==1 || b[2]==-1 && b[3]==-1 && b[1]==1 && judge(a[row][col])==-1) && b[4]==0)
        a[1][col] = 0;
    if((b[3]==1 && b[4]==1 && b[2]==-1 && judge(a[row][col])==1 || b[3]==-1 && b[4]==-1 && b[2]==1 && judge(a[row][col])==-1) && b[1]==0)
        a[2][col] = 0;
    if((b[1]==1 && b[2]==1 && b[3]==-1 && judge(a[row][col])==1 || b[1]==-1 && b[2]==-1 && b[3]==1 && judge(a[row][col])==-1) && b[4]==0)
        a[3][col] = 0;
    if((b[2]==1 && b[3]==1 && b[4]==-1 && judge(a[row][col])==1 || b[2]==-1 && b[3]==-1 && b[4]==1 && judge(a[row][col])==-1) && b[1]==0)
        a[4][col] = 0;
}
int main()
{
    int CASE = 1;
    while (~scanf("%d", &n))
    {
        init();
        for (int i = 0; i < n; i++)
        {
            int q, p;
            scanf("%d%d", &q, &p);
            int row, col;
            Find(q, row, col);
            int row2 = row + dir[p][0], col2 = col + dir[p][1];
            swap(a[row][col], a[row2][col2]);//走子
            work(row2, col2);//吃子
        }
        printf("#Case %d:\n", CASE++);
        for (int i = 1; i <= 4; i++)
        {
            for (int j = 1; j <= 4; j++)
            {
                printf("%3d", a[i][j]);
            }
            printf("\n");
        }
    }
    return 0;
}
/*
8
7 3
6 1
12 4
1 1
12 2
2 1
10 2
4 1
*/

 

 

 

 

 

 

D:规律

 

 

链接:N阶汉诺塔变形

 

思路:我不会,哈哈哈。参考大神题解勉强理解。

大神题解链接:N阶汉诺塔变形

 

#include<bits/stdc++.h>
using namespace std;
long long n, k;
int main()
{
    while (~scanf("%lld%lld", &n, &k))
    {
        vector<int> v[3];
        long long base = 1;
        for (int i = 1; i <= n; i++)
        {
            int t = (k / base) % 6;
            if (t > 2) t = 5 - t;
            v[t].push_back(i);
            base *= 3;
        }
        for (int i = 0; i < 3; i++)
        {
            if (v[i].size())
            {
                for (int j = v[i].size() - 1; j >= 0; j--)
                {
                    printf("%d", v[i][j]);
                    if (j != 0) printf(" ");
                }
            }
            else printf("0");
            printf("\n");
        }
    }
    return 0;
}
/*
3 5
4 10
*/

 

E:深搜 

 

链接:恋与程序员

 

思路:其实题意就是在一个N个点M条边的图中,求1~C的最短路,只不过边权和平时的不太一样,边权为卡片的种类,卡片只需要买一次,共有K种卡片,每张卡片有对应的花费。问1~C的最小花费。

深搜,走过的点进行标记,用过的卡片进行标记,当走某一条边的时候分两种情况,如果这个边的那张卡片买过了就不花钱,否则就需要花钱。用一个全局变量统计答案,每次走到终点C看是否需要更新答案。

当时直接想到了最短路,发现不会修改,然后就GG了。赛后看了眼别人的代码,原来是个深搜,很快就A了。

 

#include<bits/stdc++.h>
using namespace std;
const int INF = 1e9 + 5;
const int MAXN = 105;
int n, m, k, c, ans;
int G[MAXN][MAXN], card[1005];//G为图,card[i]表示编号为i的卡片价格为card[i]
bool vis[MAXN];//该点有没有被走过
bool use[1005];//卡片是否被使用
void dfs(int u, int sum)
{
    if (u == c){ ans = min(ans, sum); return ; }
    for (int v = 1; v <= n; v++)
    {
        if (G[u][v] && !vis[v])//有一条u到v的边,且v点没被走过
        {
            vis[v] = true;
            if (use[G[u][v]]) dfs(v, sum);//该卡片被用过
            else//该卡片没被用过
            {
                use[G[u][v]] = true;
                dfs(v, sum + card[G[u][v]]);
                use[G[u][v]] = false;
            }
            vis[v] = false;
        }
    }
}
int main()
{
    while (~scanf("%d%d%d%d", &n, &m, &k, &c))
    {
        ans = INF;
        memset(G, 0, sizeof(G));
        memset(vis, 0, sizeof(vis));
        memset(use, 0, sizeof(use));
        for (int i = 0; i < m; i++)
        {
            int u, v, e;
            scanf("%d%d%d", &u, &v, &e);
            G[u][v] = e;
        }
        for (int i = 0; i < k; i++)
        {
            int a, b;
            scanf("%d%d", &a, &b);
            card[a] = b;
        }
        vis[1] = true;
        dfs(1, 0);
        printf("%d\n", ans);
    }
    return 0;
}
/*
6 7 5 6
2 3 2
4 3 3
1 2 1
1 5 4
4 6 5
1 4 2
5 6 3
1 100
3 422
2 210
5 107
4 38
*/

 

 

 

 

 

F:模拟

 

 

链接:大吉大利,今晚吃鸡——跑毒篇

 

思路:朋友说有坑,我没感觉到,迷之AC。每次使离终点步数减一,步数减一之后就掉血,如果<=0那就GG,如果没有判断是否需要打血,即血量小于7*a且大于6*a,如果小于等于6*a的话打血过程中会被毒死,如果大于等于7*a的话还可以再跑一米再打血。

一个队友把使用急救包的意思理解为直接+80血了(其实是把血两补到80),结果wa到怀疑人生。

后来他说:吃了没玩过吃鸡的亏哈哈哈。

 

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int T;
    scanf("%d", &T);
    while (T--)
    {
        int a, b, c, h = 100;
        scanf("%d%d%d", &a, &b, &c);
        bool flag = true;
        b--;
        while (b--)
        {
            h = h - a;
            if (h <= 0) { flag = false; break; }
            if (6 * a < h && h <= 7 * a && c >= 1) { h = 80; c--; }
        }
        if (flag) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}
/*
3
1 100 2
6 31 2
7 31 2
*/

 

G:递归 + 找规律    

 

    

 

链接:圆圈


 

思路:估计是POJ ~ 2083的改编。图形可以分为上下左右四个部分,递归打印即可。主要是找到规律:对于每阶图形,由该阶图形左上角的点,可以推出其他下一阶图形的左上角的点的位置,如果只有一个点了那么就直接赋值为'O'。

 

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2200;
int n, row, col;//行和列的值
char a[MAXN][MAXN];
void init()//初始化
{
    n++;//下面递归中给解释
    row = pow(3, n - 1); col = pow(3, n - 1);
    memset(a, ' ', sizeof(a));//先把所有地方都置为空格
}
void draw(int n, int x, int y)//生成图形
{
    if (n == 1) { a[x][y] = 'O'; return ; }
    //如果n=0作为结束条件,那么在n=1的时候t=0.333,数组就出错了,所以我让n++且n=1的时候作为结束条件
    int t = pow(3, n - 2);
    draw(n - 1, x        , y + t    );//上
    draw(n - 1, x + t    , y        );//左
    draw(n - 1, x + t    , y + 2 * t);//右
    draw(n - 1, x + 2 * t, y + t    );//下
}
void print()//输出
{
    for (int i = 0; i < row; i++)
    {
        for (int j = col - 1; j >= 0; j--)
        {
           if(a[i][j] == 'O') { a[i][j + 1] = '\0'; break; }//控制每一行的最后没有空格
        }
    }
    for (int i = 0; i < row; i++) puts(a[i]);
}
int main()
{
    int T;
    scanf("%d", &T);
    while (T--)
    {
        scanf("%d", &n);
        init();
        draw(n, 0, 0);
        print();
    }
    return 0;
}

 

 

 

 

 

 

H:斐波那契数列

 

 

 

链接:方块与收纳盒

 

思路:听见他们说是斐波那契数列,然后就敲一发试试,发现数据不超long long,直接A了

 

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 85;
long long a[MAXN];
void Fib()
{
    a[0] = 1; a[1] = 1;
    for (int i = 2; i < MAXN; i++) a[i] = a[i - 1] + a[i - 2];
}
int main()
{
    Fib();
    int T;
    scanf("%d", &T);
    while (T--)
    {
        int t;
        scanf("%d", &t);
        printf("%lld\n", a[t]);
    }
    return 0;
}
/*
3
1
2
4
*/

 

I:暴力

 

 

 

链接:找数字个数

 

思路:求出先求出a和b数字的每一位数字,放入set。然后暴力判断1~1000,是a和b倍数的直接跳过,分理处当前数字的每一位,如果某一位在set中出现过,也跳过,否则答案++。

 

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 85;
int main()
{
    int T;
    scanf("%d", &T);
    while (T--)
    {
        int a, b;
        scanf("%d%d", &a, &b);
        int a1 = a, b1 = b;
        set<int> s;
        while (a1)
        {
            s.insert(a1 % 10);
            a1 /= 10;
        }
        while (b1)
        {
            s.insert(b1 % 10);
            b1 /= 10;
        }
        //for (auto i: s) cout << i << " "; cout << endl;
        long long ans = 0;
        for (int i = 1; i <= 1000; i++)
        {
            if (i % a == 0 || i % b == 0) { continue; }//倍数
            int t = i;
            bool flag = false;
            while (t)
            {
                if (s.count(t % 10)) { flag = true; break; }//含有某一个数字
                t /= 10;
            }
            if (flag) continue;
            ans++;
        }
        printf("%lld\n", ans);
    }
    return 0;
}
/*
3
2 3
14 23
1234 5678
*/

J:规律 + 递归

链接:闯关的lulu

 

思路:主要是题意,每走一层他会获得一个0,

2个0 == 1个1

3个1 == 1个2

4个2 == 1个3

5个3 == 1个4

。。。

可以推出来:n个k == n/(k-2)个k

他走N层总共会获得N个0,然后我们递归去算有几个几就好了,然后回溯过程种输出。

注意N的奇偶性,如果N为奇数直接在最后输一个0。

 

#include<bits/stdc++.h>
using namespace std;
int n;
void put(int n, int k)
{
    if (n == 0) return ;
    put(n / (k + 2), k + 1);//有N / (k + 2)个k+1
    n = n % (k + 2);//还有n % (k + 2)个k
    while (n--) printf("%d", k);
}
int main()
{
    int T;
    scanf("%d", &T);
    while (T--)
    {
        scanf("%d", &n);
        if (n&1){ put(n - 1, 1); printf("0"); }//奇数个0
        else put(n, 1);//偶数个0
        printf("\n");
    }
    return 0;
}
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值