2022牛客多校(七)

2022牛客多校(七)

一、比赛小结

比赛链接:"蔚来杯"2022牛客暑期多校训练营7

这场没有打,去参加 RoboCom 了

二、题目分析及解法(基础题)

C、Constructive Problems Never Die

题目链接:C-Constructive Problems Never Die

题意:

给出一个序列 A A A ,构造一个排列 P P P ,使得 A i ≠ P i , i = 1 , . . . , n A_i\not=P_i, i=1, ..., n Ai=Pi,i=1,...,n

题解:

方法很多

代码:

#include <bits/stdc++.h>
using namespace std;
int main() {
  ios_base::sync_with_stdio(false);
  int T;
  cin >> T;
  while (T--) {
    int n;
    cin >> n;
    int ok = 0;
    vector<int> a(n + 5);
    vector<int> p(n + 5);
    for (int i = 1; i <= n; i++) {
      cin >> a[i];
      if (a[i] != a[1]) ok = 1;
    }
    if (not ok) {
      cout << "NO" << endl;
      continue;
    }
    int l = 1, r = n;
    for (int i = 1; i < n; i++) {
      if (a[i] != l)
        p[i] = l, l++;
      else
        p[i] = r, r--;
    }
    p[n] = l;
    if (p[n] == a[n]) {
      for (int i = 1; i <= n - 1; i++) {
        if (p[n] != a[i]) {
          swap(p[n], p[i]);
          break;
        }
      }
    }
    cout << "YES" << endl;
    for (int i = 1; i <= n; i++) {
      cout << p[i] << " \n"[i == n];
    }
  }
  return 0;
}

F、Candies

题目链接:F-Candies

题意:

给定一串序列,有两种操作,一种是将两个相邻且相同的元素删掉,一种是将相邻且互补 (相加为 x x x )的删除,求最多可操作数

题解:

假如只有一种操作的话,那显然是贪心地删,考虑两种操作的话,思考一下局部,发现最后也是可以贪心地删。

题解给出了一种有趣的想法:

考虑能消的所有组合,(a,a),(a,x-a),(x-a,a),(x-a,x-a),相当于可以把所有大于 x/2,小于x的a看作x-a,而不影响答案。

代码:

#include <bits/stdc++.h>
using namespace std;
int main() {
  ios_base::sync_with_stdio(false);
  int n, x;
  cin >> n >> x;
  vector<int> vec;
  int cnt = 0;
  for (int i = 1; i <= n; i++) {
    int t;
    cin >> t;
    if (not vec.empty() and (vec.back() == t or vec.back() == x - t))
      vec.pop_back(), cnt++;
    else
      vec.push_back(t);
  }
  for (int l = 0, r = (int)vec.size() - 1; l < r; l++, r--) {
    if (vec[l] == vec[r] or vec[l] + vec[r] == x)
      cnt++;
    else
      break;
  }
  cout << cnt << endl;

  return 0;
}

G、Regular Expression

题目链接:G-Regular Expression

题意:

给定串S,求其最短正则匹配式以及匹配种数?匹配式可使用小写字母以及 . ? ∗ + | ( ) 这七个字符,它们的规则如下:

  • | 表示或,a|b 能匹配a也能匹配b
  • ? 表示前面的字符可有可无,a?b 能匹配ab也能匹配b
  • ∗ 表示前面的字符重复任意次(包括零)
  • + 表示前面的字符重复正整数次
  • . 表示任意字符
  • () 改变优先顺序

题解:

题目看似复杂,实际上要讨论的情况很少

容易发现,“.*”可以匹配所有串,因此答案长度 ≤ 2,只需要对1和2分别讨论。

当输入串长度为 1 时,“a” 和 “.” 可以匹配,长度为1,方案数为2。

当输入串长度 ≥ 2 时,长度为 2 ,方案数则分别考虑 “ab”,“a+”,“a.”,“a*”,“.a”,“.+”,“…”,“.*” 这几种能否匹配即可。

代码:

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

// a . *
string S;
int sn;

int main() {
  ios_base::sync_with_stdio(false);

  int Tcase;
  cin >> Tcase;
  while (Tcase--) {
    cin >> S;
    sn = S.size();

    int ans1, ans2;
    if (sn == 1) {
      ans1 = 1, ans2 = 2;
      // a
      //.
    } else  // sn>=2
    {
      ans1 = 2;
      ans2 = 0;

      if (sn == 2) ans2++;  // ab

      if (sn == 2) ans2++;  // a.

      int ok = 1;
      for (int i = 1; i < sn; i++)
        if (S[i] != S[i - 1]) ok = 0;
      ans2 += ok;  // a*

      ans2 += ok;  // a+

      if (sn == 2) ans2++;  //.a

      if (sn == 2) ans2++;  //..

      ans2++;  //.*

      ans2++;  //.+
    }

    cout << ans1 << ' ' << ans2 << '\n';
  }

  return 0;
}

J、Melborp Elcissalc

题目链接:J-Melborp Elcissalc

题意:

对于所有 k 的倍数,它们都是 good 的,一个序列的 goodness 值为其和为 good 的子段个数,求一段序列的 goodness 值

题解:

考虑问题反过来怎么解决,即给定序列怎么求有多少 子区间和 ≡ 0(mod k) 。(mod k意义下)前缀和后考虑 0 ~ k - 1 中每个数的出现次数,C_x^2 一下就是答案,很显然是 dp 可以。

又因为前缀和数组和原数组一一对应,对于原问题可以直接dp满足条件的前缀和数组有多少种。记 f ( i , j , k ) 代表考虑完了 0 ~ i 的数,放进了 j 个位置,对goodness 贡献为k 的方案数,则 f ( k - 1 , n , t ) 即为答案。

代码:

#include <bits/stdc++.h>
using namespace std;
const int MOD = 998244353;
long long poww(long long x, int y) {
  long long ret = 1;
  while (y) {
    if (y & 1) ret *= x, ret %= MOD;
    x *= x, x %= MOD;
    y >>= 1;
  }
  return ret;
}
struct modint {
  int v;
  modint() { v = 0; }
  modint(int x) { v = x % MOD; }
  modint(long long x) { v = x % MOD; }
  modint operator+(const modint &a) const { return modint((v + a.v) % MOD); }
  modint operator+=(const modint &a) { return *this = *this + a; }
  modint operator-(const modint &a) const {
    return modint((v - a.v + MOD) % MOD);
  }
  modint operator-=(const modint &a) { return *this = *this - a; }
  modint operator*(const modint &a) const { return modint(1ll * v * a.v); }
  modint operator*=(const modint &a) { return *this = *this * a; }
  modint operator/(const modint &a) const {
    return modint(1ll * v * poww(a.v, MOD - 2));
  }
  modint operator/=(const modint &a) { return *this = *this / a; }
  bool operator==(const modint &a) const { return v == a.v; }
} one(1);
modint fac[11111], ifac[11111];
modint C(int n, int m) { return fac[n] * ifac[m] * ifac[n - m]; }
modint dp[77][77][3333];
int main() {
  ios_base::sync_with_stdio(false);
  fac[0] = one;
  for (int i = 1; i <= 11000; i++) fac[i] = fac[i - 1] * i;
  ifac[11000] = one / fac[11000];
  for (int i = 11000; i >= 1; i--) ifac[i - 1] = ifac[i] * i;
  int n, k, t;
  cin >> n >> k >> t;
  for (int i = 0; i <= n; i++) {
    dp[0][i][i * (i + 1) / 2] = C(n, i);
  }
  for (int now = 1; now <= k - 1; now++) {
    for (int i = 0; i <= n; i++) {
      for (int cnt = 0; cnt <= i * (i + 1) / 2; cnt++) {
        for (int j = 0; j <= n - i; j++) {
          dp[now][i + j][cnt + j * (j - 1) / 2] +=
              dp[now - 1][i][cnt] * C(n - i, j);
        }
      }
    }
  }
  cout << dp[k - 1][n][t].v << endl;
  return 0;
}

K、Great Party

题目链接:K-Great Party

题意:

给定一个序列,代表着 n n n 堆石子,两个人来博弈,其规则为,每个人可以在这些堆石子中选取一堆,拿走一些石子,然后决定是否要将这些石子并入其他堆。

给出 q q q 次查询,每次查询给出区间 [ l , r ] [l,r] [l,r] ,询问这个区间有多少个子段可以单独拿出来作为游戏,使得先手必胜。

题解:

一个区间如果长度是奇数,那么先手必胜。否则如果石子个数减一的异或和不为0,先手必胜;否则后手必胜。

归纳证明即可。如果区间长度是1,显然先手必胜。否则如果区间长度为偶数,先后手必定不能把某一堆石子取完,不然区间长度奇偶性会发生改变,导致该玩家必败。那么每堆石子有一个是不能取的,除此之外是一个nim模板。

如果区间长度为奇数,先手显然可以对最大那堆石子操作,使其变成一个区间长度为偶数且石子个数减一的异或和为0的局面。

那么区间询问有多少子区间先手必胜则可以通过前缀异或和以及分块算法完成。时间复杂度O(n√m  )。

代码:

#include <bits/stdc++.h>
#define rep(i, x, y) for (int i = x; i <= y; ++i)

using namespace std;
const int N = 100005, M = 2000005, lim = 350;
typedef long long LL;
int n, m, a[N], id[N], s[2][M];
struct D {
  int id, l, r;
} dat[N];
LL ans[N], nw;

int getint() {
  char ch;
  while (!isdigit(ch = getchar()))
    ;
  int x = ch - 48;
  while (isdigit(ch = getchar())) x = x * 10 + ch - 48;
  return x;
}

bool cmp(D a, D b) { return id[a.l] == id[b.l] ? a.r < b.r : a.l < b.l; }

void add(int x) {
  nw += s[x & 1][a[x]];
  ++s[x & 1][a[x]];
}

void del(int x) {
  --s[x & 1][a[x]];
  nw -= s[x & 1][a[x]];
}

LL get(LL len) { return len * (len + 1) / 2; }

int main() {
  //	freopen("g.in","r",stdin);
  //	freopen("g.out","w",stdout);
  n = getint(), m = getint();
  rep(i, 1, n) a[i] = (getint() - 1) ^ a[i - 1], id[i] = (i - 1) / lim + 1;
  rep(i, 1, m) dat[i].l = getint(), dat[i].r = getint(), dat[i].id = i,
               ans[i] = get(dat[i].r - dat[i].l + 1);
  sort(dat + 1, dat + 1 + m, cmp);
  int l = 1, r = 0;
  s[0][0] = 1;
  rep(i, 1, m) {
    while (r < dat[i].r) add(++r);
    while (r > dat[i].r) del(r--);
    while (l > dat[i].l) --l, add(l - 1);
    while (l < dat[i].l) del(l - 1), ++l;
    ans[dat[i].id] -= nw;
  }
  rep(i, 1, m) printf("%lld\n", ans[i]);
  return 0;
}

三、题目分析及解法(进阶题)

不会做X

A、

B、

D、

E、

H、

I、

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值