【牛客OI周赛15-普及组】 A【模拟】B【DP】D【离散化+DP+树状数组】

比赛连接

文章目录

A

问题:长度为n的字符串,是否完全由多个mq连接组成
思路:模拟就型了。

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;

const int N = 1e5 + 11;
const int M = 1e6 + 11;
const int MOD = 1e9 + 7;

char s[N];
int main(int argc, char **args){
 // freopen("in.txt", "r", stdin);
    int q; cin >> q;
    while(q--){
        scanf("%s", s);
        int cur = 0;
        bool flag = true;
        for(int i = 0; s[i]; i++){
            if( (cur == 1 && s[i] == 'q' )|| (cur == 0 && s[i] == 'm'))
                cur = cur ^ 1;
            else 
                flag = false;
        }
        
        puts((flag && !cur) ? "Yes":"No");
    }
return 0;
}

B

思路:赛时 只想到了爆搜+剪枝,这样肯定不对,只过了50%数据。赛后又想了想,发现可以考虑DP,因为n个宝物的和最大也就是10000,取前k个最小值就行,这时候,发现,如果可以提前处理出来和为 i 的方案数,问题就解决了。
所以考虑DP,dp[i][j] 表示前 i 个宝物 的和为 j 的方案数。递推方程:
d p [ i ] [ j + a i k ] = ∑ k = 1 m i ∑ j = 0 10000 − a i k d p [ i − 1 ] [ j ] dp[i] [j + a_{ik}] =\sum_{k=1}^{m_i} \sum_{j = 0}^{10000 - a_{ik}}dp[i - 1] [j] dp[i][j+aik]=k=1mij=010000aikdp[i1][j]
发现时间复杂度正好为o(1e8),感觉时间上会过不去,但是因为这个代码的框架简单,很简洁,所以常数会非常小,所以实际上时间上还是能够过去的(而且牛客评测机1s应该很跑多了把,至少1e8常数内应该都够)。 空间上,因为第 i 层只用第 i - 1 层的值,可以利用滚动数组来优化一维。
爆搜代码:

#include <bits/stdc++.h>
using namespace std;
 
typedef long long ll;
typedef pair<int, int> pii;
 
const int N = 1e5 + 11;
const int M = 1e6 + 11;
const int MOD = 1e9 + 7;
 
int n, k;
priority_queue <int,vector<int>,less<int> > qu;
vector<int>ve[N];
void DFS(int now, int sum){
    if(qu.size() >= k && sum >= qu.top()) return; // 剪枝
    // cout << now <<" " <<sum <<"\n";
    if(now == n + 1) {
        if(qu.size() < k)
            qu.push(sum);
        else {
            if(qu.top() > sum) {
                qu.pop(); qu.push(sum);
            }
        }
        return;
    }
 
    for(int i = 0; i < ve[now].size(); i++){
        int v = ve[now][i];
        DFS(now + 1, sum + v);
    }
}
 
int main(int argc, char **args){
   // freopen("in.txt", "r", stdin);
    scanf("%d%d", &n, &k);
    for(int i = 1; i <= n ;i++){
        int m; scanf("%d", &m);
        while(m--){
            int a; scanf("%d", &a); ve[i].push_back(a);
        }
        sort(ve[i].begin(), ve[i].end());
    }
     
    DFS(1, 0);
    int ans = 0;
    while(k--){
       // cout << qu.top() <<"\n";
        ans += qu.top(); qu.pop();
    }
    printf("%d\n", ans);
return 0;
}

DP 代码

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;

const int N = 1e5 + 11;
const int M = 1e6 + 11;
const int MOD = 1e9 + 7;

ll dp[N][2];
int main(int argc, char **args){
 //   freopen("in.txt", "r", stdin);
    
    int n, k; scanf("%d%d", &n, &k);
    int cur = 0; dp[0][cur ^ 1] = 1; 
    for(int i = 1; i <= n; i++){
        int m; scanf("%d", &m);
        for(int j = 0; j <= 10000; j++) dp[j][cur] = 0;

        while(m--){
            int a;  scanf("%d", &a);
            for(int j = 0; j <= 10000; j++){
                if(dp[j][cur ^ 1] > 0) 
                    dp[j + a][cur] += dp[j][cur ^ 1];
            }
        }
        cur ^= 1;
    }

    cur ^= 1;
    int ans = 0;
    for(int i = 1; i <= 10000; i++){
        if(k > 0) {
            if(dp[i][cur] >= k) {
                ans += k * i;
            }else {
                ans += dp[i][cur] * i;
            }

            k -= dp[i][cur];
        }else break;
    }

    printf("%d\n", ans);
return 0;
}

D

思路:赛时思路对了,可惜没有发现bug。
其实很简单,从二元祖到三元组的过程中,我们可以得到启发。后面扩展到m元祖,其实本质都是一样的。考虑 dp[i][j] 表示以位置 j 开始的 i 元祖个数。转移方程:
d p [ i ] [ j ] = ∑ a k > a i & k > i d p [ i − 1 ] [ k ] dp[i][j] = \sum_{a_k > a_i \&k > i} dp[i-1][k] dp[i][j]=ak>ai&k>idp[i1][k]
如何求得右侧式子,这里可以考虑用树状数组。
因为要用树状数组,所以要将 a i a_i ai 值当做数组下标,但是原本 a i a_i ai 的值很大,发现本题和 a i a_i ai 本来值无关,只要元素间相对大小不变就行,所以用离散化。
同时观察式子,发现可以当前层只用前一层的值,所以利用滚动数组来优化 空间。

代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;

const int N = 1e5 + 11;
const int M = 1e6 + 11;
const int MOD = 1e9 + 7;

int n, m, nn; 
int sum[N];
int c[N * 4], a[N];

 
void add(int pos, int x){
    for(pos; pos > 0; pos -= (pos & -pos)){
        c[pos] = (c[pos] + x) % MOD;
    }
}

int query(int pos){
    int sum = 0;
    for(pos ; pos <= nn; pos += (pos & -pos)){
        sum = (sum + c[pos]) % MOD;
    }
    return sum;
}
vector<int>ve;
int main(int argc, char **args){
   // freopen("in.txt", "r", stdin);
    
    scanf("%d%d", &n, &m);

    for(int i = 1; i <= n; i++){
        scanf("%d", &a[i]); ve.push_back(a[i]);  
    }
    
    sort(ve.begin(), ve.end());
    ve.erase(unique(ve.begin(), ve.end()), ve.end());
    for(int i = 1; i <= n; i++){
        a[i] = lower_bound(ve.begin(), ve.end(), a[i]) - ve.begin() + 1; // 离散化,重新赋值
        sum[i] = 1;// 赋初值
    } 

    nn = n * 2;
    
    for(int j = 2; j <= m; j++){ 
        memset(c, 0, sizeof(c));
        for(int i = n; i > 0; i--){
            add(a[i] , sum[i]);
            sum[i] = query(a[i] + 1);
            // cout << sum[i] <<" "; 
        }
    }
    
    
    if(m == 1){
        cout << n <<"\n";
    }else {
        int ans = 0;
        for(int i = 1; i <= n; i++) {
            ans = (ans + sum[i]) % MOD;
        }
        printf("%d\n", ans);
    }
        
return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值