牛客NowCoder OI周赛普及组15题解

牛客NowCoder OI周赛普及组15题解

A咪咪游戏

A题略

B.三角形

题目:在这里插入图片描述

思路

两种思路,堆和背包dp。

先看堆的,维护一个含有K个元素的最大堆,逐个遍历每个箱子的宝物,每次只维护价值之和是前K小的,因为比这些元素大的必定不会出现在之后的结果里。

具体就是,先把堆中元素全部弹出,存入一个数组中,然后依次从大到小令其中的元素同当前宝箱中的宝物的价值相加,将重新得到元素加入堆中。

#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
int a[110], b[10010];
priority_queue<int> pq; // 维护一个大根堆
 
int main() {
    int n, k;
    cin >> n >> k;
    pq.push(0); //细节 开始时堆不能为空。
    while(n--) {
        int m; cin >> m;
        for (int i = 1; i <= m; i++) cin >> a[i];
        sort(a + 1, a + 1 + m);
        int cnt = 0;
        while (!pq.empty()) {
            b[++cnt] = pq.top(); pq.pop();
        }
        for (int i = 1 ; i <= m; i++) {
            for (int j = cnt; j >= 1; j--) {
                if (pq.size() < k) pq.push(a[i] + b[j]);
                else if (pq.top() > a[i] + b[j]) {
                    pq.pop(); pq.push(a[i] + b[j]);
                }
                else break; 
            }
        }
    }
    int cnt = 0;
    while (!pq.empty()) {
        b[++cnt] = pq.top(); pq.pop();
    }
    int ans = 0;
    for (int i = 1; i <= k; i++) ans += b[i];
    cout << ans << endl;
    return 0;
}
dp

dp思路,转化成背包dp。

d p [ i ] [ j ] dp[i][j] dp[i][j]代表在前i个宝箱中取物品,价值和为j的方案数。

转移方程:
d p [ i ] [ j ] = d p [ i ] [ j ] + d p [ i − 1 ] [ j − v a l [ k ] ] , ∀   k ∈   b o x [ i ] dp[i][j]=dp[i][j] + dp[i-1][j-val[k]], \forall\ k\in \ box[i] dp[i][j]=dp[i][j]+dp[i1][jval[k]], k box[i]

可以用滚动数组优化。

最后统计答案的时候就是从从小到大遍历 j j j然后相加。

#include <iostream>
#include <cmath>
using namespace std;
#define N 105
int dp[N][N*N], a[N][N];
int main(){
    int n, k, tot = 0;
    cin>>n>>k;
    for(int i = 1; i <= n; i++){
        int num = 0;
        cin>>a[i][0];
        for(int j = 1; j <= a[i][0]; j++){
            cin>>a[i][j];
            num = max(num, a[i][j]);
        }
        tot += num;
    }
    dp[0][0] = 1;
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= a[i][0]; j++)
            for(int m = tot; m >=a[i][j]; m--){
                if(dp[i-1][m-a[i][j]])
                    dp[i][m] += dp[i-1][m-a[i][j]];
            }
            
    int ans = 0, cnt = 0;
    for(int i = 1; i <=tot; i++){
        while(dp[n][i]){
            ans += i;
            cnt++;
            dp[n][i]--;
            if(cnt == k){
                cout<<ans<<endl;
                return 0;
            }
        }
    }
    return 0;
}

C.区间加

题目

在这里插入图片描述
计数类问题,一般考虑dp。

首先做个差得到数组 a a a a i a_i ai代表原始数组中第 i i i个元素同目标值的差距,根据题目的限制,我们可以得到下面的重要的结论:
对于数组中相邻的两个元素其差值不能超过1

那么我们对数组 a a a做个差分,得到数组 b b b,那么对于 b b b数组,两个相邻的数组元素差值也不可能超过1,根据这个我们可以做dp。

如何解决这个问题呢,我们把问题转化成在一段数字两边加括号的方案数,那么对于一个元素 i i i,他左边所有的左括号与右括号的差值,即为这个数被加1的次数(因为这就代表了他被包含在了多少个加1区间里面)。

d p [ i ] dp[i] dp[i]代表处理到第i个数时的方案数。有以下几种情况。
(注意 b [ i ] = a [ i ] − a [ i − 1 ] b[i]=a[i]-a[i-1] b[i]=a[i]a[i1])

1)如果 b [ i ] = = − 1 b[i]==-1 b[i]==1,说明第 i i i个元素比 i − 1 i-1 i1个元素大一,那么他必定比他前一个元素要少加一次,所以此时一定要在 i i i的前面加一个右括号,那么加的这个右括号可能会和前面的任意一个括号匹配,我们令 u n M a t c h unMatch unMatch代表目前为止仍未被匹配的左括号的数目,那么 d p [ i ] = d p [ i − 1 ] ∗ ( u n M a t c h ) dp[i]=dp[i-1]*(unMatch) dp[i]=dp[i1](unMatch),然后被匹配的左括号多了1, u n M a t c h unMatch unMatch减1。

2)如果 b [ i ] = = 1 b[i]==1 b[i]==1,说明第 i i i个元素比第 i − 1 i-1 i1个元素小一,那么他就要比前面一个元素多加一次,所以应该在他前面放一个左括号。则 d p [ i ] = d p [ i − 1 ] dp[i]=dp[i-1] dp[i]=dp[i1] u n M a t c h unMatch unMatch加1。

3)如果 b [ i ] = = 0 b[i]==0 b[i]==0,说明两个元素相等,那么两种方案,什么都不做,和在 i i i前面分别放一个左括号和有括号,算上两种情况是 d p [ i ] = d p [ i − 1 ] ∗ ( u n M a t c h + 1 ) dp[i] =dp[i-1]*(unMatch+1) dp[i]=dp[i1](unMatch+1) u n M a t c h unMatch unMatch保持不变,因为两种操作均不会改变未被匹配的括号数。

在稍微判断下题目中的-1情况即可。

#include <iostream>
using namespace std;
 
const int mod = 998244355;
int a[2010], b[2010];
int n, m;
 
int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        a[i] = m - a[i];
    }
     
    for (int i = 1; i <= n; i++)
        b[i] = a[i] - a[i-1];
     
    long long ans = 1, unMatch = 0;
    for (int i = 1; i <= n; i++) {
        if (abs(b[i]) > 1) {
            puts("0");
            return 0;
        }
        if (b[i] == 1)
            unMatch++;
        else if (b[i] == -1)
            ans = (ans * unMatch--) % mod;
        else
            ans = (ans * (unMatch + 1)) % mod;
    }
    cout << ans << endl;
    return 0;
}

D.多元组

题目

在这里插入图片描述

思路

还是动态规划,挺明显的,可以用前 M − 1 M-1 M1元素来推 M M M元组,递推式:
d p [ i ] [ k ] = ∑ j d p [ j ] [ k − 1 ]   ( a [ j ] < a [ i ] ) dp[i][k]=\sum\limits_{j}dp[j][k-1]\quad\ (a[j]<a[i]) dp[i][k]=jdp[j][k1] (a[j]<a[i])
但是如果用直接写复杂度会达到 O ( n 2 ) O(n^2) O(n2),所以需要快速的得到这个和,那么考虑用k个树状数组去维护 a [ i ] a[i] a[i]前面的元素的 k − 1 k-1 k1元素的和

#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int mod = 1e9+7;
const int max_n = 1e5+10;
 
ll a[max_n], b[max_n];;
ll bit[max_n][55], dp[max_n][55];
int n, m;
 
int lowbit(int i) {
    return i & -i;
}
void add(int i, int val, int k) {
    while (i <= n) {
        bit[i][k] += val;
        i += lowbit(i);
    }
}
 
ll sum(int i, int k) {
    ll s = 0;
    while (i > 0) {
        s += bit[i][k];
        i -= lowbit(i);
    }
    return s;
}
 
int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        b[i] = a[i];
    }
         
    sort(b+1, b+1+n);
    int len = unique(b+1, b+1+n)-b-1;
     
    for (int i = 1; i <= n; i++) {
        a[i] = lower_bound(b+1, b+1+len, a[i])-b; // 离散化,但是为了避免出现0下标,减去b。
    }
     
    for (int i = 1; i <= n; i++) dp[i][1] = 1; // 每个数都是一个一元组。
     
    for (int i = 1; i <= n; i++) {
        add(a[i], 1, 1); 
        for (int k = 2; k <= m; k++) {
            dp[i][k] = (dp[i][k] + sum(a[i]-1, k-1)) % mod;
            add(a[i], dp[i][k], k);
        }
    }
     
    ll ans = 0;
    for (int i = 1; i <= n; i++)
        ans = (ans + dp[i][m]) % mod;
     
    cout << ans << endl;   
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值