2022牛客寒假集训营3

题目链接:link

A.智乃的Hello XXXX

签到题1

D.智乃的01串打乱

签到题2

B.智乃买瓜(easy)

题目

水果摊上贩卖着 \(N\) 个不同的西瓜,第 \(i\) 个西瓜的重量为 \(w_i\)

智乃对于每个瓜都可以选择买一个整瓜或者把瓜劈开买半个瓜,半个瓜的重量为 \(\frac{w_i}{2}\)

智乃想要知道,如果他想要购买西瓜的重量和分别为 \(k=1,2,\cdots,M\) 时,有多少种购买西瓜的方案

分析

背包问题的简单变形:

\[dp[i][j]=dp[i-1][j]+dp[i-1][j-w_i]+dp[i-1][j-\frac{w_i}{2}] \]

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

const int MOD = 1e9 + 7;
const int MAX_N = 1000 + 5;
int n, m;
int w[MAX_N];
int dp[MAX_N][MAX_N];

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
        cin >> w[i];
    dp[0][0] = 1;
    for(int i = 1; i <= n; i++) {
        for(int j = 0; j <= m; j++) {
            dp[i][j] = dp[i - 1][j];
            if(j >= w[i] / 2)
                dp[i][j] = (dp[i][j] + dp[i - 1][j - w[i] / 2]) % MOD;
            if(j >= w[i])
                dp[i][j] = (dp[i][j] + dp[i - 1][j - w[i]]) % MOD;
        }
    }
    for(int i = 1; i <= m; i++)
        cout << dp[n][i] << " ";
    cout << endl;
    return 0;
}

L.智乃的数据库

题目

给定一张由 \(N\) 条记录和 \(M\)int 类型的字段组成的数据表,请你执行一个 SELECT COUNT(*) FROM Table GROUP BY ...; 的查询语句,请你把查询的结果告诉智乃

分析

模拟题

代码
#include<bits/stdc++.h>
using namespace std;
 
const int MAX_N = 1000 + 5;
int n, m;
string s[MAX_N];
string cmd;
int a[MAX_N][MAX_N];
vector<int> idx;
map<string, int> mt;
bool vis[MAX_N];
int ans[MAX_N];
 
bool cmp(int a1, int a2)
{
    for(int i = 0; i < idx.size(); i++)
        if(a[a1][idx[i]] != a[a2][idx[i]])
            return false;
    return true;
}
 
int main()
{
    cin >> n >> m;
    for(int i = 1; i <= m; i++) {
        cin >> s[i];
        mt.insert(make_pair(s[i], i));
    }
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            cin >> a[i][j];
    getchar();
    getline(cin, cmd);
    int l = 36;
    for(int i = 36; i < cmd.size(); i++) {
        if(cmd[i] == ',') {
            string tmp = cmd.substr(l, i - l);
            idx.push_back(mt[tmp]);
            l = i + 1;
        }
    }
    string tmp = cmd.substr(l, cmd.size() - l - 1);
    idx.push_back(mt[tmp]);
    int cnt = 0;
    for(int i = 1; i <= n; i++) {
        if(!vis[i]) {
            cnt++;
            ans[cnt]++;
            vis[i] = true;
            for(int j = i + 1; j <= n; j++) {
                if(cmp(i, j)) {
                    vis[j] = true;
                    ans[cnt]++;
                }
            }
        }
    }
    cout << cnt << endl;
    for(int i = 1; i <= cnt; i++)
        cout << ans[i] << " ";
    cout << endl;
    return 0;
}

E.智乃的数字积木(easy)

题目

智乃酱有 \(N\) 块积木,每一块积木都有自己的颜色以及数字,这 \(N\) 块积木颜色的范围从 \(1\)\(M\) ,数字的范围从 \(0\)\(9\)
现在智乃酱把这些积木从左到右排成一排,这样积木看上去就形成了一个大整数,智乃觉得这个数字不够大,所以他决定交换一些积木使得这个大整数尽可能的大

具体来讲,智乃可以在任意时刻无限次的交换相邻且同色的数字积木
但是即使这样,智乃觉得这个数字还是不够大

所以智乃酱拿出了她的油漆桶,她决定进行 \(K\) 次操作,每次操作都选中两种颜色 \(P,Q\) ,然后将所有颜色为 \(P\) 的积木染成颜色 \(Q\)
当然,在染色结束后智乃酱也是可以交换相邻同色积木进行调整

现在智乃想要知道,她进行 \(K\) 次染色操作之前,以及每次染色后能够通过交换得到最大的正整数是多少

分析

由于相邻同色的积木可以任意交换,当一个连续的同颜色色块的数字从大到小排序时,能取到这一色块产生的最大值,那么对于序列中的所有色块都进行一次排序,就能使序列取最大值

由于 \(K\leq 10\) ,所以在每次染色后都执行一次上述的操作即可,复杂度 \(O((K+1)\times N\log{N})\)

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
 
const int MAX_N = 100000 + 5;
const int MOD = 1e9 + 7;
int n, m, k;
string s;
int col[MAX_N];
 
int calc()
{
    ll ans = 0, pw = 1;
    for(int i = n - 1; i >= 0; i--) {
        ans = (ans + pw * (s[i] - '0')) % MOD;
        pw = pw * 10 % MOD;
    }
    return ans;
}
 
void work()
{
    int l = 0, r = 0;
    for(int i = 0; i < n - 1; i++) {
        if(col[i] == col[i + 1]) {
            r = i + 1;
            if(i + 1 == n - 1) {
                sort(s.begin() + l, s.begin() + r + 1, greater<char>());
            }
        } else {
            r = i;
            sort(s.begin() + l, s.begin() + r + 1, greater<char>());
            r = l = i + 1;
        }
    }
}
 
int main()
{
    cin >> n >> m >> k >> s;
    for(int i = 0; i < n; i++)
        cin >> col[i];
    work();
    cout << calc() << endl;
     
    while(k--) {
        int p, q;
        cin >> p >> q;
        for(int i = 0; i < n; i++)
            if(col[i] == p)
                col[i] = q;
        work();
        cout << calc() << endl;
    }
    return 0;
}

I.智乃的密码

题目

某网站的密码必须符合以下几个条件

  • 仅包含大小写英文字母,数字,和特殊符号
  • 长度大于等于 \(L\) 且小于等于 \(R\)
  • 密码中至少应该包括 ①大写英文字母,②小写英文字母,③数字,④特殊符号 这四类字符中的三种

现在给定一个长度为 \(N\) 的字符串 \(S\) ,求 \(S\) 中有多少个子串是一个符合条件的密码

分析

对于一个索引为 \(i\) 的字符,我们要求使子串 \((i,j)\) 满足作为密码的条件的最小的 \(j\) ,那么子串 \((i,j+1),(i,j+2),\cdots\) 也满足条件,就能得到以 \(i\) 为子串的第一个字符所组成的满足条件的子串数

考虑如何求 \(j\) ,首先我们将原字符串的每一个字符都替换成他们所代表的字符类型,设 \(b[i][k]\) 表示从索引 \(i\) 开始到字符串结尾,类型为 \(k\) 的字符出现的最早索引,稍加思考就可以得出:将 \(b[i][1],b[i][2],b[i][3],b[i][4]\) 从小到大排序后第三大的 \(b[i][k]\) 就是要求的 \(j\)

数组 \(b\) 的预处理可以在 \(O(n)\) 的时间里完成,总复杂度也为 \(O(n)\)

代码
#include<bits/stdc++.h>
using namespace std;
 
const int MAX_N = 100000 + 5;
const int INF = 0x3f3f3f3f;
int n, l, r;
int a[MAX_N];
int b[MAX_N][5];
string s;
 
 
int pd(char ch)
{
    if(ch >= 'A' && ch <= 'Z')
        return 1;
    else if(ch >= 'a' && ch <= 'z')
        return 2;
    else if(ch >= '0' && ch <= '9')
        return 3;
    return 4;
}
 
int mfind(int st, int val)
{
    for(int i = st; i <= n; i++)
        if(a[i] == val)
            return i;
    return -1;
}
 
int getminn(int idx)
{
    int tmp[5];
    for(int i = 1; i <= 4; i++)
        tmp[i] = b[idx][i];
    sort(tmp + 1, tmp + 4 + 1);
    return tmp[3];
}
 
int main()
{
    cin >> n >> l >> r >> s;
    for(int i = 1; i <= n; i++)
        a[i] = pd(s[i - 1]);
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= 4; j++) {
            if(i > b[i - 1][j]) {
                b[i][j] = mfind(b[i - 1][j] + 1, j);
                b[i][j] = (b[i][j] == -1 ? INF : b[i][j]);
            } else {
                b[i][j] = b[i - 1][j];
            }
        }
    }
    long long ans = 0;
    for(int i = 1; i <= n; i++) {
        int res = max(i + l - 1, getminn(i));
        if(res <= i + r - 1 && res <= n) {
            ans = ans + min(n, i + r - 1) - res + 1;
        }
    }
    cout << ans << endl;
    return 0;
}

G.智乃的树旋转

题目

输入两棵二叉树,其中第二棵树由第一棵树经过不超过一次的树旋转过后形成,问如何操作使其还原回第一棵树

分析

每次旋转后,父子结点的关系都会被改变,而旋转次数又不超过一次,所以可以双重循环枚举结点 \(i,j\) 如果在第一颗树中结点 \(i\) 的父节点是 \(j\) 且在第二棵树中结点 \(j\) 的父节点是 \(i\) ,那说明他们在旋转后互换了,再旋转一次即可还原

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

const int MAX_N = 1000 + 5;
struct tree {
    int fa;
    int ch[2];
} t1[MAX_N], t2[MAX_N];
int n;

void build(tree * t)
{
    int x, y;
    for(int i = 1; i <= n; i++) {
        scanf("%d%d", &x, &y);
        t[i].ch[0] = x;
        t[i].ch[1] = y;
        if(x)
            t[x].fa = i;
        if(y)
            t[y].fa = i;
    }
}

int main()
{
    scanf("%d", &n);
    build(t1);
    build(t2);
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= n; j++) {
            if(t1[j].fa == i && t2[i].fa == j) {
                printf("1\n%d\n", i);
                return 0;
            }
        }
    }   
    printf("0\n");
    return 0;
}

C.智乃买瓜(hard)

题目

在原题的购买规则上,智乃现在知道购买重量和为 \(1,2,\cdots ,M\) 的方案数,构造一个贩卖 \(N\) 个不同西瓜的水果摊,使方案数符合输入

分析

根据原来的转移方程:

\[dp[i][j]=dp[i-1][j]+dp[i-1][j-w_i]+dp[i-1][j-\frac{w_i}{2}] \]

可以推出:

\[dp[i][j]=dp[i+1][j]-dp[i][j-w_i]-dp[i][j-\frac{w_i}{2}] \]

相当于一开始给了 \(dp[n][1],dp[n][2],\cdots,dp[n][m]\) 从后向前推

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int MOD = 1e9 + 7;
const int MAX_M = 1000 + 5;
ll dp[MAX_M];
int m;
vector<int> ans;

int main()
{
    cin >> m;
    dp[0] = 1;
    for(int i = 1; i <= m; i++)
        cin >> dp[i];
    for(int i = 1; i <= m; i++) {
        while(dp[i]) {
            ans.push_back(2 * i);
            for(int j = 0; j <= m; j++) {
                if(j + i <= m)
                    dp[j + i] = (dp[j + i] - dp[j] + MOD) % MOD;
                if(j + 2 * i <= m)
                    dp[j + 2 * i] = (dp[j + 2 * i] - dp[j] + MOD) % MOD;
            }
        }
    }
    cout << ans.size() << endl;
    for(int i = 0; i < ans.size(); i++)
        cout << ans[i] << " ";
    cout << endl;
    return 0;
}

J.智乃的C语言模除方程

题目

给定 \(P,l,r,L,R\) 求:

\[\sum_{i=L}^R \sum_{j=l}^r [i\bmod P=j] \]

分析

考虑原问题的简单版本,求:

\[sum(x,y)=\sum_{i=1}^x \sum_{j=1}^y [i\bmod P=j] \]

容易知道: \(sum(P-1,y)=\min(P-1,y)\) ,记 \(t=\min(P-1,y)\)

由模的循环性可知原式进行了 \(\lfloor\frac{x+1}{P}\rfloor\) 次完整循环,最后一次不完整的循环进行了 \(x-P\lfloor\frac{x+1}{P}\rfloor=(x+1)\bmod P-1\) 次,可以得到:

\[sum(x,y)=t\lfloor\frac{x+1}{P}\rfloor+\min(t,(x+1)\bmod P-1) \]

\(l,r,L,R\) 均为正数时,可以用二维前缀和的方式解决这一问题

\(l\leq0\leq r\) 时,可以发现在上式中并没有计算 \(i\bmod P=0\) 产生的贡献,在这里我们需要加上

\[L\leq kP\leq R\Rightarrow \lceil\frac{L}{P}\rceil\leq k\leq \lfloor\frac{R}{P}\rfloor \]

\(k\) 的个数为 \(\lfloor\frac{R}{P}\rfloor-\lceil\frac{L}{P}\rceil+1=\lfloor\frac{R}{P}\rfloor-\lfloor\frac{L-1}{P}\rfloor\)

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

ll P, l, r, L, R, ans = 0;

ll sum(ll x, ll y)
{
    ll t = min(P - 1, y);
    return (x + 1) / P * t + min((x + 1) % P - 1, t);
}

ll calc(ll a, ll b, ll c, ll d)
{
    return sum(a, b) - sum(c - 1, b) - sum(a, d - 1) + sum(c - 1, d - 1);
}

int main()
{
    cin >> P >> l >> r >> L >> R;
    P = abs(P);
    if(l <= 0 && r >= 0) {
        ll base = ((ll)1e10) / P * P;
        ans += (base + R) / P - (base + L - 1) / P;
    }
    if(R > 0 && r > 0)
        ans += calc(R, r, max(1ll, L), max(1ll, l));
    if(L < 0 && l < 0)
        ans += calc(-L, -l, max(1ll, -R), max(1ll, -r));
    cout << ans << endl;
    return 0;
}
posted @ 2022-01-30 19:04  f(k(t))  阅读(78)  评论(0编辑  收藏  举报