2022牛客寒假算法基础集训营3_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ
目录
A-智乃的Hello XXXX
解题思路:签到题,直接输出即可。
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
cout << "hello world" << endl;
return 0;
}
B-智乃买瓜
解题思路:简单背包问题。定义为考虑到第 i 个西瓜时的总重量为 j 的方案数。根据题目要求,状态转移方程为
,对于后两项注意判断 j 和 w[i] 的关系,初始化
,则
到
即为答案。
AC代码(未优化):
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int mod = 1000000007;
const int N = 1005, M = 1005;
int dp[N][M], w[N];
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int n, m;
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] >= 0) dp[i][j] = (dp[i][j] + dp[i - 1][j - w[i]]) % mod;
if(j - w[i] / 2 >= 0) dp[i][j] = (dp[i][j] + dp[i - 1][j - w[i] / 2]) % mod;
}
}
for(int i = 1; i < m; i++) cout << dp[n][i] << ' ';
cout << dp[n][m] << endl;
return 0;
}
AC代码(优化):
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int mod = 1000000007;
const int M = 1005;
int dp[M];
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i++){
int w;
cin >> w;
dp[0] = 1;
for(int j = m; j >= w / 2; j--){
if(j >= w) dp[j] = (dp[j] + dp[j - w]) % mod;
dp[j] = (dp[j] + dp[j - w / 2]) % mod;
}
}
for(int i = 1; i <= m; i++) cout << dp[i] << ' ';
return 0;
}
C-智乃买瓜(another version)
解题思路:逆向思维,由于瓜的重量一定是偶数,所以总重量为 1 一定是购买重量为 2 的瓜所得到的,即如果 dp[1] 为 1,那么一定存在一个重量为 2 的瓜(重量最小瓜)。那么每次得到最小的瓜之后就需要更新当前这个瓜会造成影响的 dp 数组,这样才能将这个瓜拿出并消除它造成的影响,这时下一个 dp 数组不为 0 的点就一定是对应买第二小的瓜的一半(因为下一个 dp 数组的点对应的重量的瓜已经被拿走并消除影响了),然后再将这个瓜拿出……
考虑到上一题的转移方程,当前这个瓜造成的影响有三种:第一项表示不买当前瓜,第二项表示买当前瓜,第三项表示买当前瓜的一半。消去当前瓜所造成的影响,就相当于求不买当前瓜的方案数,也就是
。可以通过移项并让
,得到逆转移方程
。最后将二维 dp 数组优化成一维即可。
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int mod = 1000000007;
const int N = 1005;
int dp[N], ans[N];
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int m, cnt = 0;
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]){ // dp[i] != 0表示一定存在2*i重量的瓜
ans[cnt++] = 2 * i;
for(int j = 0; j <= m - i; j++){
// 更新买2*i的瓜对后面dp数组的影响
if(2 * i + j <= m) dp[2 * i + j] = (dp[2 * i + j] - dp[j] + mod) % mod;
// 更新买2*i的瓜的一半对后面dp数组的影响
dp[i + j] = (dp[i + j] - dp[j] + mod) % mod;
}
}
}
cout << cnt << endl;
for(int i = 0; i < cnt; i++) cout << ans[i] << ' ';
return 0;
}
D-智乃的01串打乱
解题思路:签到题。遍历一遍字符串,将任意的0和1交换即可。
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int n;
cin >> n;
string s;
cin >> s;
for(int i = 1; i < n; i++){
if(s[i] != s[0]){
swap(s[i], s[0]);
break;
}
}
cout << s << endl;
return 0;
}
E-智乃的数字积木(easy version)
解题思路:要使得数字最大,那就是将可以交换的一段区间里的数按9~0的顺序进行排序,可以交换的条件是颜色相同,所以就遍历一遍颜色,然后遇到相邻颜色不同的就分段,同时将前面这一段的数字逆序排序。由于数字很大,将数字用字符串的形式存储,输出答案时应将字符转成数字取模再输出。
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
#include <functional>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int mod = 1000000007;
const int N = 100005;
int col[N];
int n, m, k;
char s[N];
void op(){
int l = 0;
int x = col[0];
for(int i = 1; i <= n; i++){
if(col[i] != x){
sort(s + l, s + i, greater<char>());
l = i;
x = col[i];
}
}
ll ans = 0;
for(int i = 0; i < n; i++){
ans = (ans * 10 % mod + s[i] - '0') % mod;
}
cout << ans << endl;
}
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
cin >> n >> m >> k;
cin >> s;
for(int i = 0; i < n; i++) cin >> col[i];
col[n] = -1;
op();
while(k--){
int p, q;
cin >> p >> q;
for(int i = 0; i < n; i++){
if(col[i] == p) col[i] = q;
}
op();
}
return 0;
}
F-智乃的数字积木(hard version)
解题思路:对于每一个块(连续的颜色相同的积木),统计每个数字出现的次数。为了让一个块中的数字最大,那一定是形如 9998877654333210 这样的数字序列,将每种数字单独拎出来,可以发现如 333000 这样一个数可以表示成 ,一般式为
,那么这样就可以将每种数字的个数进行一通计算作为开始的下标 n,然后数字作为 m,就可以
计算任意一个块中的答案。再考虑染色后合并的问题,可以考虑启发式合并思想,每次将较小的块往较大的块上合并,这样每次只需要遍历较小的块的元素直接加到较大的块上,由于每次合并结束后再找较小的块一定会是之前的两倍以上,因此时间复杂度
,其中颜色的不同可以用并查集进行维护。最后,对于这种维护全局答案的类型,可以不用每次重新计算一遍答案,只需要在上次修改的基础上对于这次修改进行更改即可。
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
#include <vector>
#include <set>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int mod = 1000000007;
const int N = 100005;
char s[N];
int fa[N], cnt[N][10]; // cnt[i][j]表示第i个块中j数字的个数
ll pw[N], ans; // pw[i]表示10^i
int n, m, k;
vector<int> col(N);
vector<set<int> > vs(N);
int find(int x){
if(fa[x] != x) fa[x] = find(fa[x]);
return fa[x];
}
ll quick_pow(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 fun(int i){
ll res = 0;
for(int j = i, k = 9; k >= 0; k--){
j += cnt[i][k];
res = (res + k * (pw[cnt[i][k]] - 1) * quick_pow(9, mod - 2) % mod * pw[n - j + 1]) % mod;
}
return res;
}
void merge(int x){
int u = find(x), v = find(x + 1);
if(u == v) return;
ans = ((ans - fun(u) + mod) % mod - fun(v) + mod) % mod;
fa[v] = u;
for(int i = 0; i <= 9; i++){
cnt[u][i] += cnt[v][i];
}
ans = (ans + fun(u)) % mod;
}
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
scanf("%d%d%d", &n, &m, &k);
scanf("%s", s + 1);
pw[0] = 1;
for(int i = 1; i <= n; i++){
scanf("%d", &col[i]);
vs[col[i]].insert(i);
fa[i] = i;
pw[i] = pw[i - 1] * 10 % mod;
ans = (ans * 10 + (s[i] - '0')) % mod;
cnt[i][s[i] - '0']++;
}
for(int i = 1; i < n; i++){
if(col[i] == col[i + 1]) merge(i);
}
printf("%lld\n", ans);
while(k--){
int u, v;
scanf("%d%d", &u, &v);
if(u != v){
if(vs[u].size() > vs[v].size()) swap(vs[u], vs[v]);
for(auto x : vs[u]){
if(vs[v].count(x - 1)) merge(x - 1);
if(vs[v].count(x + 1)) merge(x);
vs[v].insert(x);
}
vs[u].clear();
}
printf("%lld\n", ans);
}
return 0;
}
G-智乃的树旋转(easy version)
解题思路:由于本题操作数限定为 ,那么如果可以旋转回去,操作数为1;旋转不回去操作数为0。判断能否旋转回原样就是能否找到一对结点的父子关系发生交换。若找到,则以原树的父结点为轴旋转即可。
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 1005;
int t[N], t1[N];
int lch, rch;
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int n;
cin >> n;
for(int i = 1; i <= n; i++){
cin >> lch >> rch;
t[lch] = i;
t[rch] = i;
}
for(int i = 1; i <= n; i++){
cin >> lch >> rch;
t1[lch] = i;
t1[rch] = i;
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
if(t[i] == j && t1[j] == i){
cout << 1 << endl << j << endl;
return 0;
}
}
}
cout << 0 << endl;
return 0;
}
H-智乃的树旋转(hard version)
解题思路:从根开始先序遍历原来的二叉树中每个结点,如果该结点在打乱的树中需要还原(不在原位上),则用splay伸展树进行操作将其旋转至原位,然后打上lazy标记表示该结点已经恢复原位,不再进行旋转。每次旋转时将要旋转的结点信息存入答案数组中。
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
#include <vector>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 1005;
bool vis[N], lazy[N]; // vis[i]表示i结点是否有父亲
// lazy[i]表示i结点是否已被还原
vector<int> ans;
struct tree{
int fa; // 父亲结点信息
int ch[2]; // 0表示左孩子,1表示右孩子
}t[N], t1[N];
void rotate(int x){ // x是要旋转的结点
int y = t1[x].fa;
int z = t1[y].fa;
int k = (t1[y].ch[1] == x);
t1[z].ch[(t1[z].ch[1] == y)] = x;
t1[x].fa = z;
t1[y].ch[k] = t1[x].ch[k ^ 1];
t1[t1[x].ch[k ^ 1]].fa = y;
t1[x].ch[k ^ 1] = y;
t1[y].fa = x;
ans.push_back(x);
}
void splay(int x){
while(!lazy[t1[x].fa]){
int y = t1[x].fa;
int z = t1[y].fa;
if(z && !lazy[z]){
(t1[z].ch[0] == y) ^ (t1[y].ch[0] == x) ? rotate(x) : rotate(y);
}
rotate(x);
}
}
void dfs(int root){ // 先序遍历原树中的每个结点
splay(root);
lazy[root] = true;
if(t[root].ch[0]) dfs(t[root].ch[0]);
if(t[root].ch[1]) dfs(t[root].ch[1]);
}
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int n, root;
cin >> n;
for(int i = 1; i <= n; i++){
int x, y;
cin >> x >> y;
vis[x] = vis[y] = true;
t[i].ch[0] = x;
t[i].ch[1] = y;
if(x) t[x].fa = i;
if(y) t[y].fa = i;
}
for(int i = 1; i <= n; i++){
int x, y;
cin >> x >> y;
t1[i].ch[0] = x;
t1[i].ch[1] = y;
if(x) t1[x].fa = i;
if(y) t1[y].fa = i;
}
for(int i = 1; i <= n; i++){ // 找根结点
if(!vis[i]){
root = i;
break;
}
}
lazy[0] = true;
dfs(root);
cout << ans.size() << endl;
for(vector<int>::iterator it = ans.begin(); it != ans.end(); it++){
cout << *it << endl;
}
return 0;
}
I-智乃的密码
解题思路:将四种类型用1~4存储,表示前 i 个字符中 j 类字符的数量。遍历密码长度左端点,二分答案,判断条件为是否存在三种字符,存在就往左二分,不存在就往右二分。每次二分结束后把区间长度累加起来即为答案。
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 100005;
int a[N], dp[N][5];
char s[N];
bool check(int l, int r){
int c1 = dp[r][1] - dp[l - 1][1];
int c2 = dp[r][2] - dp[l - 1][2];
int c3 = dp[r][3] - dp[l - 1][3];
int c4 = dp[r][4] - dp[l - 1][4];
if(c1 && c2 && c3 || c1 && c2 && c4 || c1 && c3 && c4 || c2 && c3 && c4) return true;
return false;
}
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int n, L, R;
cin >> n >> L >> R;
cin >> s + 1;
for(int i = 1; i <= n; i++){
if(s[i] >= 'A' && s[i] <= 'Z') a[i] = 1;
else if(s[i] >= 'a' && s[i] <= 'z') a[i] = 2;
else if(s[i] >= '0' && s[i] <= '9') a[i] = 3;
else a[i] = 4;
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= 4; j++){
dp[i][j] = dp[i - 1][j] + (a[i] == j);
}
}
ll cnt = 0;
for(int i = 1; i <= n - L + 1; i++){
int r = min(i + R - 1, n);
int l = i;
if(check(l, r)){
while(l < r){
int mid = l + r >> 1;
if(check(i, mid)) r = mid;
else l = mid + 1;
}
l = max(l, i + L - 1);
cnt += min(i + R - 1, n) - l + 1;
}
}
cout << cnt << endl;
return 0;
}
J-智乃的C语言模除方程
解题思路:由于[l, r]和[L, R]均可能包含正数、负数和零,首先最好将讨论的范围全部移到正半轴。
第一种情况是零存在于[l, r]区间中,那么单独计算当 Q 为 0 时,满足 的解的个数,即[L, R]中 p 的整数倍的个数(这里可以学习一种比较妙的方法,设定一个足够大且为p的整数倍的数base,L和R加上后就一定能转移到正半轴,排除负数向下取整的问题)。
第二种情况是存在正半轴的数,那么直接计算正半轴的数即可。
第三种情况是存在负半轴的数,那么将其取负号转成正半轴的数再进行计算。
上面三种情况分别对应标程中的三个if语句,这样就把整个Q区间和答案区间都考虑完了。
接下来就是如何计算的问题了,可以考虑 x 存在于答案区间[L, R],Q存在于取值区间[l, r],那么要同时满足这两个条件,也就是考虑一个二元函数在满足对应区间的和。这就很容易想到可以用二维前缀和来求解。
所以答案为,
其中,
。最后前缀和的求解参照下面代码。
AC代码(参照标程):
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
ll p, l, r, L, R, ans;
ll sum(ll x, ll q){
q = min(p - 1, q);
ll round = (x + 1) / p; // 循环节个数
ll last = (x + 1) % p; // 最后一个不完全循环节的元素数
return (q + 1) * round + min(last, q + 1) - 1;
// q + 1表示循环节中的元素数,min(last, q + 1)表示最后不完全循环节中需要的元素数,-1是因为要去掉0这个数
}
ll cal(ll l, ll r, ll L, ll R){
return sum(R, r) - sum(L - 1, r) - sum(R, l - 1) + sum(L - 1, l - 1);
}
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
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 += cal(max((ll)1, l), r, max((ll)1, L), R);
}
if(L < 0 && l < 0){
ans += cal(max((ll)1, -r), -l, max((ll)1, -R), -L);
}
cout << ans << endl;
return 0;
}
K-智乃的C语言模除方程(another version)
解题思路:由于该题的方程变为,P为给定的值,因此只需要考虑 P 的正负即可,当 P 为负数时,将 P 取反,并将 Q 的区间取到正半轴;当 P 为正数时,直接取 Q 的正半轴区间。接下来由于 x 是变量,
,可以使用整除分块进行枚举,每次枚举的时候
是个定值,这样
就是一个等差数列,那么问题就转换成等差数列的第 L 项到第 R 项中有多少项是在 Q 的取值范围 [l, r] 中。
AC代码(参照标程):
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
ll p, l, r, L, R, ans;
ll segment_intersect(ll lx, ll rx, ll lq, ll rq){ // 取区间交集
ll l = max(lx, lq);
ll r = min(rx, rq);
return max((ll)0, r - l + 1);
}
ll cal(ll x, ll y, ll base, ll len){
if(y == 0) return l <= x && x <= r ? segment_intersect(L, R, base, base + len) + segment_intersect(L, R, -(base + len), -base) : 0;
ll s = x > r ? (x - r + y - 1) / y : 0; // 项数左端点偏移量
ll t = x >= l ? (x - l) / y : -1; // 项数右端点偏移量
t = min(t, len - 1);
return segment_intersect(L, R, base + s, base + t) + segment_intersect(L, R, -(base + t), -(base + s));
}
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
cin >> p >> l >> r >> L >> R;
if(p < 0){
p = -p;
l = -l;
r = -r;
swap(l, r);
}
for(ll l = 1, r; l <= p; l = r + 1){
r = p / (p / l);
ans += cal(p % l, p / l, l, r - l + 1);
}
ans += cal(p, 0, p + 1, 1000000000);
cout << ans << endl;
return 0;
}
L-智乃的数据库
解题思路:按题目意思模拟即可。由于题目的数据比较多,要注意字符串的输入和各个数据的存储,下面代码参考过题大佬的代码(原因绝对不是自己的AC代码太乱了)
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
#include <vector>
#include <map>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 1005, M = 1005;
int a[N][M];
map<string, int> mp;
vector<int> v, v1;
map<vector<int>, int> mp2;
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int n, m;
cin >> n >> m;
for(int i = 1; i <= m; i++){
string name;
cin >> name;
mp[name] = i;
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
cin >> a[i][j];
}
}
string s, q;
while(cin >> s){
if(s == "BY"){
cin >> s;
break;
}
}
int len = s.size();
for(int i = 0; i < len; i++){
if(s[i] == ',' || s[i] == ';'){
v.push_back(mp[q]);
q.clear();
}
else q += s[i];
}
for(int i = 1; i <= n; i++){
v1.clear();
int vlen = v.size();
for(int j = 0; j < vlen; j++){
v1.push_back(a[i][v[j]]);
}
mp2[v1]++;
}
cout << mp2.size() << endl;
for(map<vector<int>, int>::iterator it = mp2.begin(); it != mp2.end(); it++){
cout << it->second << ' ';
}
return 0;
}