2021年CCPC河南省赛部分题解

2825: 收集金币(状态机dp)

题目链接
状态机模型的dp
技能的使用与否作为两种状态
f[i][0]表示进行到第i个事件没有使用技能
f[i][1]表示进行到第i个事件使用了技能

状态转移方程:
LOST事件:
f[i][1] = max (f[i - 1][0], max (0LL, f[i - 1][1] - val)) (第i次使用技能和前i-1次使用技能的最大值)
f[i][0] = max (0LL, f[i - 1][0] - val)
GET事件:
f[i][1] = f[i - 1][1] + val (得到金币一定不需要使用技能所以技能的使用一定是前i-1次用的)
f[i][0] = f[i - 1][0] + val

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 20010;
LL f[N][2];
int main ()
{
    int T; cin >> T;
    while (T --)
    {
        memset (f, 0, sizeof f);
        int n; cin >> n;
        for (int i = 1; i <= n; i ++)
        {
            string o; int val;
            cin >> o >> val;
            if (o[0] == 'L')
            {
                f[i][1] = max (f[i - 1][0], max (0LL, f[i - 1][1] - val));
                f[i][0] = max (0LL, f[i - 1][0] - val);
            }
            else{
                f[i][1] = f[i - 1][1] + val;
                f[i][0] = f[i - 1][0] + val;
            } 
              
        }
        cout << max (f[n][0], f[n][1]) << "\n";   
    }
    return 0;
}

2826: 使用技能(乘法逆元+组合数学)

题目链接
题目求期望值,等概率,m种技能,选n个并且可重复,所以分母很好确定为m^n次方,对于分子,可以技能选取个数入手,例如某个技能选取i个则情况数位C(n, i) * (m - 1)^(n -i),后面一项的意思是剩下的n-i个技能从剩下的m-1种技能中选择,因为求的是技能伤害,还要乘上伤害i^2,所以最后的结果应为:C(n, i) * (m - 1)^(n -i) * i^2 * m / m^n (1<=i<=n)

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, mod = 1e9 + 7;
typedef long long LL;
LL fac[N], infac[N];
 
LL qmi (LL a, LL b)
{
    LL res = 1;
    while (b)
    {
        if (b & 1) res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}
 
LL C (LL a, LL b)
{
    return fac[a] * infac[b] % mod * infac[a - b] % mod;
}
int main ()
{   
    fac[0] = infac[0] = 1;
    for (int i = 1; i < N; i ++)
    {
        fac[i] = fac[i - 1] * i % mod;
        infac[i] = infac[i - 1] * qmi (i, mod - 2) % mod;
    }
    int T; cin >> T;
    while (T --){
        int n, m; cin >> n >> m;
        LL res = 0;
        for (int i = 1; i <= n; i ++)
            res = (res + (LL)i * i % mod * m % mod * C (n, i) % mod * qmi (m - 1, n - i) % mod) % mod; 
        res = res * qmi (qmi (m, n), mod - 2) % mod;
        cout << res << "\n";
    }
    return 0;
}

2827: 欢度佳节(位运算+枚举)

题目链接
共有17个格子,可以用二进制枚举每个状态,判断每个状态是不是联通

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, mod = 1e9 + 7;
typedef long long LL;
typedef pair <int, int > PII;
PII p[20];
int cnt[N], g[20][20];
int sum = 0, ans = 0;
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, 1, 0, -1};
void init()
{
    p[0] = {1, 1}, p[1] = {1, 2}, p[2] = {1, 4}, p[3] = {1, 5};
    p[4] = {2, 2}, p[5] = {2, 3}, p[6] = {2, 4}; 
    p[7] = {3, 2}, p[8] = {3, 3}, p[9] = {3, 4}; 
    p[10] = {4, 2}, p[11] = {4, 3}, p[12] = {4, 4}; 
    p[13] = {5, 1}, p[14] = {5, 2}, p[15] = {5, 4}, p[16] = {5, 5};
}
 
void dfs (int x, int y)
{
    sum ++;
    for (int i = 0; i < 4; i ++)
    {
        int xx = x + dx[i], yy = y + dy[i];
        if (xx < 1 || xx > 5 || yy < 1 || yy > 5 || g[xx][yy] == -1) continue;
        g[xx][yy] = -1;
        dfs (xx, yy);
    }
}
 
int main ()
{   
    init ();
    int T; cin >> T;
    while (T --)
    {
        ans = 0;
        for (int i = 0; i < 17; i ++)
        {
            cin >> cnt[i];
            cnt[i] = (cnt[i] - 1) / 6 + 1;
        }
        int n; cin >> n;
        for (int i = 0; i < 1 << 17; i ++)
        {
            if (!(i >> 13 & 1)) continue;  
 
            memset (g, -1, sizeof g);
            vector <int> v;
            for (int j = 0; j < 17; j ++)
            {
                if (i >> j & 1)
                {
                    g[p[j].first][p[j].second] = j;
                    v.push_back (j);
                }
            }
            sum = 0;
            g[p[13].first][p[13].second] = -1;
            dfs (p[13].first, p[13].second);
            if (sum ==(int) v.size ())
            {
                int tot = 0;
                for (auto t : v)
                    tot += cnt[t];
                if (tot <= n) ans = max (ans, sum);
            }
        }
        cout << ans << "\n";
    }
    return 0;
}

2829: 闯关游戏 (DP)

题目链接
f[i][j]:前i关消耗体力j的最大价值
根据由Ai转移还是Ci转移可分成两种状态:
因为第i-1关不进行操作直接游戏结束,因此第i关一定是由i-1转移,因此可以用滚动数组实现,具体见代码:
f[i][j] = max ( f[i - 1 & 1][j - a[i]] + b[i], f[i - 1 & 1][j - c[i]] + d[i])

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, mod = 1e9 + 7;
typedef long long LL;
typedef pair <int, int > PII;
int a[N], b[N], c[N], d[N];
int f[2][N];
int main ()
{   
    int T; cin >> T;
    while (T --)
    {
        int n, m;
        cin >> n >> m;
        for (int i = 1; i <= n; i ++)
            cin >> a[i] >> b[i] >> c[i] >> d[i];
         
        int res = 0;
        for (int i = 0; i <= m; i ++) f[0][i] = -1;
        f[0][0] = 0;
        for (int i = 1; i <= n; i ++)
            for (int j = 0; j <= m; j ++)
            {
                int k = -1;
                if (j >= a[i] && f[i - 1 & 1][j - a[i]] >= 0)
                    k = f[i - 1 & 1][j - a[i]] + b[i];
                if (j >= c[i] && f[i - 1 & 1][j - c[i]] >= 0)
                    k = max (k, f[i - 1 & 1][j - c[i]] + d[i]);
                f[i & 1][j] = k;
                res = max (res, f[i & 1][j]); 
            }
        cout << res << "\n";
    }
    return 0;
}

2834: 小凯的书架 (树状数组+二分)

题目链接
根据书的高度由大到小进行插入,id数组维护的是这本书前面的书的位置。

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int a[N], id[N], res[N];
int tr[N];
int n, k;
int lowbit (int x)
{
    return x & -x;
}
void add (int x, int v)
{
    for (int i = x; i <= n; i += lowbit (i)) tr[i] += v;
}
 
int query (int x)
{
    int res = 0;
    for (int i = x; i > 0; i -= lowbit (i)) res += tr[i];
    return res;
}
int main ()
{
    int T; cin >> T;
    while (T --)
    {
        memset (tr, 0, sizeof tr);
        memset (res, 0, sizeof res);
        cin >> n >> k;
        for (int i = 1; i <= n; i ++)
        {
            cin >> a[i];
            id[i] = i;
        }
        sort(id+1,id+n+1,[&](int i,int j){
           return a[i]>a[j]; 
        });
         
        for (int i = 1; i <= n; i ++)
        {
            //由大到小的顺序进行插入
            int num = query (id[i]);
            if (num < k)
            {
                res[id[i]] = -1;
                add (id[i], 1);
                continue;
            }
            int pre = num - k + 1, l = 1, r = id[i] - 1;
            while (l < r)
            {
                int mid = (l + r) >> 1;
                if (query (mid) < pre) l = mid + 1;
                else    r = mid;
            }
            res[id[i]] = a[r];
            add (id[i], 1);
        }
        for (int i = 1; i <= n; i ++)
            cout << res[i] << "\n";
    }
}

2835: 未成年人之友(模拟)

题目链接

#include <bits/stdc++.h>
using namespace std;
string s[7] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
int main ()
{
    int n;
    cin >> n;
    while (n --)
    {
        int age; string date;
        cin >> age >> date;
        int hh, mm;
        scanf ("%d:%d", &hh, &mm);
        if (age >= 18)
        {
            puts ("Yes");
            continue;
        }
        int i = 0;
        for (i = 0; i < 7; i ++)
        {
            if (s[i] == date)
                break;
        }
        if (i < 4)
        {
            puts ("No");
            continue;
        }
 
        if (hh >= 21 || hh < 20)
        {
            puts ("No");
            continue;
        }
        puts ("Yes");
     
    }
 
}

2836: 黑曜石 (思维)

题目链接
只需要知道一点:水和岩浆中间没有黑曜石阻隔便可以生成黑曜石

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int f[N][2];
struct node {
    int hight;
    int kind;
}q[N];
 
bool cmp (node a, node b)
{
    return a.hight > b.hight;
}
int main ()
{
    int T; cin >> T;
    while (T --)
    {
        int n; cin >> n;
        for (int i = 1; i <= n; i ++)
            cin >> q[i].kind >> q[i].hight;
        sort (q + 1, q + 1 + n, cmp);
        int cnt = 0;
        int last = -1;
        for (int i = 1; i <= n; i ++){
            if (q[i].kind == 3)
                cnt ++;
            if (i == 1) continue;
            if ((q[i- 1].kind == 1 && q[i].kind == 2) ||q[i- 1].kind ==2 && q[i].kind ==1 )
                cnt ++;
        }
        cout << cnt << "\n";
    }
    return 0;
}
  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值