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 \}\)

可以得到如下的状态转移。

\[fath_{i,j} = fath_{fath_{i, j - 1}, j - 1} \]

\[f_{i, j} = f_{i, j - 1} + f_{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}\} \]

然后枚举每个位置,求每个位置至多打 m 场比赛的最大价值。

求位置的最大价值的时候,可以倍增求解,注意每次加入一个区间的时候,在第 x 个位置处的答案:

\[ans_x = max\{ sum + g_{x + t, j}\} \]

其中 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;
}
posted @ 2024-04-18 21:00  Kersen  阅读(43)  评论(1)    收藏  举报