2022 陕西 省赛
顺序按照我认为的难度排序
J
题目描述
有一个长度为 n 的数列 \(a_i\) 。有一个数 k,表示可以将 k 个 \(a_i\) 减 1,可以连续可以不连续。问你可以将所有的数都变成相同的数的 k 最大是多少。
思路
\(k = 1\) 的时候,相当于把一个数减去 1,\(k = n - 1\) 的时候,相当于将任意的数加上 1。所以除了所有的数都相等的情况答案为 \(n\) 之外,其他所有的情况都是 \(n-1\) 。
code
//
// Created by kersen on 24-4-14.
//
//
// Created by kersen on 24-4-14.
//
#include <bits/stdc++.h>
#define N 100010
//
using namespace std;
int n, a[N];
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
int sum = 0;
for (int i = 2; i <= n; i++)
if (a[i] == a[1]) sum++;
if (sum == n - 1) cout << n << "\n";
else cout << n - 1;
return 0;
}
B
题目描述
一个翻牌游戏。
给出两个长度为 \(n\) 的数列,分别叫做 \(a_i\) \(b_i\) 表示卡牌两面的数字大小。给出一个 k 表示可以将任意的 \(a_i\) 变成 \(b_i\) 的次数。变成 \(b_i\) 之后就不能再变回 \(a_i\) 。
有 \(Q\) 次询问,每次询问都会给出 m 个不可以翻转的卡牌,对于每次询问给出最大的牌上的数字之和。
思路
对于所有的卡牌,求出 \(b_i - a_i\) 表示为 \(ch\) ,并让卡牌按照 \(ch\) 由大到小排序。然后求出初始的翻 k 张牌的价值和。对于每次询问,会给出 m 张不可以翻的卡牌,让卡牌按照 ch 拍完序,然后从前往后枚举,如果这张牌在不可以翻的 k 张牌之内,就手动移除,更新一下价值。
code
//
// Created by kersen on 24-4-14.
//
#include <bits/stdc++.h>
#define N 100010
#define lson rt << 1
#define rson rt << 1 | 1
#define int long long
//
using namespace std;
int n, k, q, m;
map<int, bool> ma;
map<int, int> mb;
struct node {
int len, sum, lazy;
}tree[N << 2];
struct PPP {
int a, b, c, id;
}num[N];
struct OOO {
int id, pat;
}qu[N];
int read() {
int s = 0, f = 0; char ch = getchar();
while (!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while (isdigit(ch)) s = s * 10 + (ch ^ 48), ch = getchar();
return f ? -s : s;
}
bool cmp(PPP a, PPP b) {
return a.c > b.c;
}
void push_up(int rt) {
tree[rt].sum = tree[lson].sum + tree[rson].sum;
}
void build(int rt, int l, int r) {
tree[rt].len = r - l + 1, tree[rt].lazy = 0;
if (l == r) {
if (l <= k) {
tree[rt].sum = num[l].b;
ma[num[l].id] = 1;
}
else tree[rt].sum = num[l].a;
return;
}
int mid = (l + r) >> 1;
build(lson, l, mid);
build(rson, mid + 1, r);
push_up(rt);
}
void change(int rt, int l, int r, int pos, int c) {
if (l == r) {
tree[rt].sum = c;
return;
}
int mid = (l + r) >> 1;
if (pos <= mid) change(lson, l, mid, pos, c);
else change(rson, mid + 1, r, pos, c);
push_up(rt);
}
int query(int rt, int l, int r, int L, int R) {
if (L <= l && r <= R) return tree[rt].sum;
int mid = (l + r) >> 1, ans = 0;
if (L <= mid) ans += query(lson, l, mid, L, R);
if (R > mid) ans += query(rson, mid + 1, r, L, R);
return ans;
}
bool cmp1(OOO a, OOO b) {
return a.pat < b.pat;
}
signed main() {
n = read(), k = read();
for (int i = 1; i <= n; i++) num[i].a = read();
for (int i = 1; i <= n; i++) {
num[i].b = read();
num[i].c = num[i].b - num[i].a;
num[i].id = i;
}
sort(num + 1, num + n + 1, cmp);
for (int i = 1; i <= n; i++)
mb[num[i].id] = i;
build(1, 1, n);
int ans = tree[1].sum;
q = read();
for (int i = 1; i <= q; i++) {
m = read();
// cout << m << "\n";
queue<int> q, p;
int thisk = k;
for (int j = 1; j <= m; j++) {
qu[j].id = read();
qu[j].pat = mb[qu[j].id];
// cout << qu[j].id << " " << qu[j].pat << "\n";
}
// puts("");
sort(qu + 1, qu + m + 1, cmp1);
// for (int j = 1; j <= m; j++) cout << qu[j].pat << "\n";
// puts("");
int thisans = ans;
for (int j = 1; j <= m; j++) {
// cout << qu[j].pat << "\n";
if (qu[j].pat <= thisk) {
thisans -= num[qu[j].pat].b;
thisans += num[qu[j].pat].a;
q.push(qu[j].pat);
thisk++;
thisans -= num[thisk].a;
thisans += num[thisk].b;
p.push(thisk);
}
}
printf("%lld\n", thisans);
}
return 0;
}
G
题目描述
给出一个 n,m。表示石头数量和水的数量。在二维平面中用石头组成一个上方开口的容器。问你 n 个石头可以围出最多多少个水,m个水需要最少多少个石头围出。
思路
纯纯画图猜结论题。
赛场上队友怕爆 unsigned long long 然后就开始用 python 写,结果因为 python 不熟练,出现了很多奇奇怪怪的问题,然后又换成 C++ 结果不会爆,这下纯纯小糖人了。
code
//
// Created by kersen on 24-4-14.
//
#include <bits/stdc++.h>
#define int unsigned long long
using namespace std;
signed main() {
int n, m;
cin >> n >> m;
int k = 0;
int ans = 0;
if (n % 2 == 0) {
k = n / 2 - 1;
ans = k * (k + 1);
} else {
k = (n - 1) / 2;
ans = k * k;
}
if (ans >= m) {
cout << ans;
} else {
int k1 = ceil((sqrt(4 * m + 1) - 1) / 2);
int n1 = (k1 + 1) * 2;
int k2 = ceil(sqrt(m));
int n2 = k2 * 2 + 1;
cout << min(n1, n2) << '\n';
}
return 0;
}
C
题目描述
给出 n 个字符串和一个价值 k。要打出者所有的字符串。在打每个字符串的时候有两种选择。
1、手动把所有的字符串都敲出来,每敲一个字的价值是 1,这样的价值就是 \(length_i\) 。
2、将之前所有的打过的字符串选择一个复制一下粘贴过来(有且仅能复制一次),这下的操作价值是 k 。然后对复制过来的字符串进行删减一个字符的价值是 1, 在任意的地方添加的价值也是 1 。
所有的字符串可以按照不同的顺序进行敲打。
问你将所有的字符串都打出来的最小价值是多少。
思路
一个很浅显的道理是,可以先两两求出所有的字符串 \(i,j\) 之间的 \(lcs\) 那么如果已经打出了 \(s_i\) 那么用第二种选择的价值就是 \(length_i - lcs_{ij} + length_j - lcs_{i,j}\) 。
可以抽象的来想一下。
对于所有的字符串抽象成点,然后建立一个虚点,虚点和其他点连边的边权设置为各个字符串的长度,然后字符串与字符串之间的连边设置为上述价值。
然后对所有的点跑最小生成树就可以得到最小的价值。
code
#include <bits/stdc++.h>
#define N 110
#define int long long
using namespace std;
int n, k, add_edge, fa[N];
struct PPP {
int len;
char s[N];
}str[N];
struct node {
int a, b, dis;
} edge[N * N];
void add(int from, int to, int dis) {
edge[++add_edge].a = from;
edge[add_edge].b = to;
edge[add_edge].dis = dis;
}
int get_dis(int x, int y) {
int f[N][N];
for (int i = 0; i <= str[x].len; i++)
for (int j = 0; j <= str[y].len; j++)
f[i][j] = 0;
for (int i = 1; i <= str[x].len; i++)
for (int j = 1; j <= str[y].len; j++)
if (str[x].s[i] == str[y].s[j]) f[i][j] = f[i - 1][j - 1] + 1;
else f[i][j] = max(f[i - 1][j], f[i][j - 1]);
return k + str[x].len + str[y].len - f[str[x].len][str[y].len] * 2;
}
bool cmp(node a, node b) {
return a.dis < b.dis;
}
int father(int x) {
if (x != fa[x]) fa[x] = father(fa[x]);
return fa[x];
}
signed main() {
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> str[i].len;
for (int j = 1; j <= str[i].len; j++)
cin >> str[i].s[j];
// scanf("%s", str[i].s + 1);
// cin >> len >> a[i];
add(n + 1, i, str[i].len);
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (i != j) {
add(i, j, get_dis(i, j));
}
sort(edge + 1, edge + add_edge + 1, cmp);
for (int i = 1; i <= n + 1; i++) fa[i] = i;
int cnt = 0, dis = 0;
for (int i = 1; i <= add_edge; i++) {
int fx = father(edge[i].a), fy = father(edge[i].b);
if (fx != fy) {
fa[fx] = fy;
cnt++;
dis += edge[i].dis;
}
if (cnt == n) break;
}
cout << dis << "\n";
return 0;
}
D
题目描述
多组询问。
每组给出一个 s。S是T的前缀,并且S与T的hash值相同。
问你T能是多少。
思路
暴力枚举所有长度 \(\leq 6\) 的串以后会发现可以构造出所有不同的 hash 值,于是直接把原串向左移动 6 位, 给前面拼 个字母使得 hash 值不变即可。
直接暴力的话复杂度是不对的(主要是这个模数速度挺慢的),可以考虑考虑这样一个事实:既然这 6 位 的哈希值 X 是多少已知,我们直接枚举它是几倍的 mod+X,然后把他还原回原串,如果每一位都在 区间 [1-26]之间,就是一组合法解。这样的解非常密集,枚举一次的成功率大约是 (26/29)^6 = 51%, 所以只需要枚举几个数就能得到解。
code
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 5999993;
int gethash(string s) {
int ret = 0;
for (int i = 0; i < s.length(); i++) {
char ch = s[i];
ret = (ret * 29 + (ch - 'a' + 1)) % mod;
}
return ret;
}
void unhash(int x) {
char res[1003];
int cnt = 0;
while (x > 0) {
res[cnt++] = (char)(x % 29 + 'a' - 1);
x /= 29;
}
for (int i = cnt - 1; i >= 0; i--) cout << res[i];
}
bool check(int x) {
int cnt = 0;
while (x > 0) {
char ch = x % 29 + 'a' - 1;
if (ch < 'a' || ch > 'z') return false;
cnt++, x /= 29;
}
return cnt == 6;
}
int q_pow(int a, int b) {
int ans = 1;
while (b) {
if (b & 1) ans = (ans * a) % mod;
a = (a * a) % mod;
b >>= 1;
}
return ans;
}
signed main() {
int t;
cin >> t;
while (t--) {
string s;
cin >> s;
int A = gethash(s);
bool flag = false;
int B = A;
while(true) {
B = (B * (int) q_pow(29, 6)) % mod;
for (int i = 0; i <= 100; i++) {
int B1 = A + mod * i - B;
if (check(B1)) {
cout << s;
unhash(B1);
cout << '\n';
flag = true;
break;
}
}
if (flag) break;
}
}
return 0;
}
H
题目描述
给你一个长度为 \(n\) 的数列 \(a_i\) 。让你将这个数列划分成两个部分,我们称之为 \(pre\) 和 \(suf\) 。
令 \(pre\) 的长度为 \(A\) ,\(suf\) 的长度为 \(B\) 。然后让所有 suf 中的数对 pre 中的数取模数,如果所有的数都相同,那么就可以这样划分。
问你 A 的最大值是多少。
思路
首先对数列 \(a_i\) 进行排序。可以发现:
1.假设所有兔子的最小值为:\(x\) ,将所有不为 \(x\) 的兔子全部变为绿色。因为小的值对大的值求余,结果一定是小的值。所以所有 \(x\) 对大于 \(x\) 的值求余,结果都是 \(x\) 、满足题意。即假设最小值出现次数为 \(cnt\),则答案为 \(n-cnt\)。
2.若最大值为 \(x\),且 \(x\) 对所有非 \(x\) 的数字求余结果相同。那么可以将所有非 \(x\) 的兔子涂成绿色。例如 2 2 2 3 4 13 13,可以将 2 2 2 3 4 全部涂绿,两个 13 对所有其他数字求余得到的结果都是 1 .即假设最大值的出现次数为 \(cnt\),则答案为 \(n-cnt\)。
3.若最大值对其它数字求余结果均为 0 ,答案为 n-1 。例如 2 2 3 3 6 6 12 12。可以只剩个 12 为白兔子,其它的兔子全部涂成绿色.
4.先将所有数字排序,以某一数字 \(x\) 为分割点,所有小于等于 \(x\) 的兔子涂绿,其余兔子保持白色.这种情况最难判断,首先我们需要证明除此之外没有其它情况(即一定是以某一个数字作为分割点,白色兔子和绿色兔子分为两堆)
然后求前缀的 \(\text{lcm}\) ,其中 \(Q_i\) 表示钱 i 个数字的最小公倍数,然后再求后缀差值的 \(gcd\) , \(P_i\) 表示 \(i ~ n\) 这些数字差值的 \(\text{gcd}\) 。
然后枚举 \(i\),若 \(P_{i + 1} \% Q_i = 0\) 则说明前 \(i\) 只兔子涂色是一种可行的方案。
code
#include <bits/stdc++.h>
#define N 100010
#define M 1000010
#define int long long
using namespace std;
int n, a[N], num[M];
int p[N], q[N];
int read() {
int s = 0, f = 0; char ch = getchar();
while (!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while (isdigit(ch)) s = s * 10 + (ch ^ 48), ch = getchar();
return f ? -s : s;
}
int gcd(int a, int b) {
if (!b) return a;
else return gcd(b, a % b);
}
int lcm(int a, int b) {
return a * b / gcd(a, b);
}
signed main() {
n = read();
for (int i = 1; i <= n; i++) {
a[i] = read();
num[a[i]]++;
}
if (num[a[1]] == n || num[a[1]] == 1) {
cout << n - 1;
return 0;
}
sort(a + 1, a + n + 1);
int ans = 0;
ans = max(n - num[a[1]], ans);
q[1] = a[1];
int tot = 2;
for (int i = 2; i <= n; i++) {
q[i] = lcm(q[i - 1], a[i]);
if (q[i] <= a[n]) tot++;
}
for (int i = n - 1; i >= 1; i--) {
p[i] = gcd(p[i + 1], a[i + 1] - a[i]);
}
p[n] = a[n];
for (int i = 1; i <= n - 1 && i <= tot - 1; i++)
if (p[i + 1] % q[i] == 0) ans = max(ans, i);
cout << ans << "\n";
}
F
题目描述
一个人要打 CF ,现在给出所有比赛的开始时间和这个人要打当前这场的 rating 加减情况,分别用 \(a_i\) 和 \(d_i\) 表示开始时间和 rating 改变。每打一场比赛需要 k 的时间缓缓,这段时间内不能打任何比赛,如果打了第 i 场比赛,可以在 \(a_i + k + 1\) 的时候打下一场。
这个人打比赛的方式是,在一个时刻如果没有在休息,如果有比赛打,那就会立即打这场比赛,也可以在当前这个时刻决定放弃,那么剩下的比赛就一场也不打。
这个人至多可以打 m 场,也可以不打满 m 场。
可以选择任意时刻开始打比赛,问你上述条件下,打比赛的最大价值是多少。
思路
比较裸地倍增维护。
维护三个数组 \(fath_{i,j} \ f_{i,j} \ g_{i,j}\) 。
\(fath_{i,j}\) 表示从第 i 个位置钦定他打 \(2^j\) 场比赛之后紧接着可以打的比赛是哪一场。
\(f_{i,j}\) 表示从第 i 个位置开始钦定他打 \(2^j\) 场比赛的价值是多少。
\(g_{i,j}\) 表示从第 i 个位置开始最多打满 \(2^j\) 场比赛的最大价值是多少。
一开始算 \(fath_{i, 0}\) 的时候可以枚举每个位置,然后二分查找第一个大于等于 \(a_i + k + 1\) 的位置是哪里,也可以 lower_bound 求。
\(f_{i, 0} = d_i\)
\(g_{i, 0} = max \{0, d_i \}\)
可以得到如下的状态转移。
然后枚举每个位置,求每个位置至多打 m 场比赛的最大价值。
求位置的最大价值的时候,可以倍增求解,注意每次加入一个区间的时候,在第 x 个位置处的答案:
其中 sum 求的是从 x 枚举到此处的 \(f_{x, i}\) 之和,好像还不太好解释,具体看代码。
code
#include <bits/stdc++.h>
#define N 200010
#define M 20
#define int long long
using namespace std;
int n, m, k, two[M];
int nex[N], f[N][M], g[N][M], fath[N][M];
struct node {
int a, d;
}thi[N];
int read() {
int s = 0, f = 0; char ch = getchar();
while (!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while (isdigit(ch)) s = s * 10 + (ch ^ 48), ch = getchar();
return f ? -s : s;
}
signed main() {
two[0] = 1;
for (int i = 1; i <= 18; i++) two[i] = two[i - 1] * 2;
n = read(), m = read(), k = read();
for (int i = 1; i <= n; i++) thi[i].a = read();
for (int i = 1; i <= n; i++) thi[i].d = read();
thi[n + 1].a = 1e9 + 1, thi[n + 2].a = 1e9 + 2;
for (int i = 1; i <= n; i++) {
int l = 1, r = n + 2;
while (l < r) {
// cout << l << " " << r << "\n";
int mid = (l + r) >> 1;
if (thi[mid].a >= thi[i].a + k + 1) r = mid;
else l = mid + 1;
}
if (l <= n) fath[i][0] = l;
else fath[i][0] = 0;
}
for (int i = 1; i <= n; i++) {
f[i][0] = thi[i].d;
g[i][0] = max(0ll, thi[i].d);
}
for (int j = 1; j <= 18; j++)
for (int i = 1; i <= n; i++) {
f[i][j] = f[i][j - 1] + f[fath[i][j - 1]][j - 1];
fath[i][j] = fath[fath[i][j - 1]][j - 1];
g[i][j] = max(g[i][j - 1], f[i][j - 1] + g[fath[i][j - 1]][j - 1]);
}
int ans = 0;
for (int i = 1; i <= n; i++) {
int cnt = 0, x = i, flag = 0, sum = 0, tes = m;
for (int j = 18; j >= 0; j--)
if (two[j] <= tes) {
tes -= two[j];
if (!flag) {
flag = 1;
cnt = max(cnt, g[x][j]);
} else cnt = max(cnt, sum + g[x][j]);
sum += f[x][j];
x = fath[x][j];
ans = max(ans, cnt);
if (x == 0) break;
}
}
cout << ans << "\n";
return 0;
}