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\) 时,有多少种购买西瓜的方案
分析
背包问题的简单变形:
代码
#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[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(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\) 次,可以得到:
当 \(l,r,L,R\) 均为正数时,可以用二维前缀和的方式解决这一问题
当 \(l\leq0\leq r\) 时,可以发现在上式中并没有计算 \(i\bmod P=0\) 产生的贡献,在这里我们需要加上
\(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;
}