AtCoder Grand Contest 045 部分题目简要题解

从这里开始

  因为巨大多无可奉告的原因,所以咕得非常厉害。有空再补 EF 好了。

Problem A Xor Battle

  考虑如果 1 能选的某个数 $a$ 后面 0 能选的数都能表示出它,显然是 0 必胜。

  否则考虑考虑到这一位的异或和为 $s$,如果 0 能获胜,那么说明 $s XOR a$ 以及 $s$ 都能被后面的数线性表示出来,这推出 $a$ 能被线性表示出来,这显然和条件矛盾。因此此时 1 必胜。

  感觉做复杂了,去看瞄了一眼题解,直接 dp 用线性基维护一下 dp 值为真的位置就行了。不会 dp 石锤了。

Code

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

#define ll long long

const int bzmax = 60;

typedef class LinearBasis {
  public:
    ll a[bzmax];
  
    void insert(ll x) {
      for (int i = bzmax; i-- && x; ) {
        if ((x >> i) & 1) {
          x ^= a[i];
        }
        if ((x >> i) & 1) {
          a[i] = x;
          break;
        }
      }
    }
    bool query(ll x) {
      for (int i = bzmax; i-- && x; ) {
        if ((x >> i) & 1) {
          x ^= a[i];
        }
      }
      return !x;
    }
    
    void reset() {
      memset(a, 0, sizeof(a));
    }
} LinearBasis;

const int N = 205;

int T, n;
ll a[N];
char s[N];
LinearBasis lb;

void solve() {
  lb.reset();
  scanf("%d", &n);
  for (int i = 1; i <= n; i++) {
    scanf("%lld", a + i);
  }
  scanf("%s", s + 1);
  for (int i = n; i; i--) {
    if (s[i] == '1') {
      if (!lb.query(a[i])) {
        puts("1");
        return;
      }  
    } else {
      lb.insert(a[i]);
    }
  }
  puts("0");
}

int main() {
  scanf("%d", &T);
  while (T--) {
    solve();
  }
  return 0;
}

Problem B 01 Unbalanced

  当没有通配符的时候,把 0 替换为 -1,考虑前缀和,显然答案是最大前缀和和最小前缀和的差。

  考虑先把通配符全部填成 1。从右到左依次考虑每个通配符填成的 1,如果把它换成 -1 能够使得最大的和减少 2,那么就换。假如不操作这一步,那么把之后操作的一步放到这里来显然不会更劣。

  这样有点小问题,因为可以使得最大的和减少 1。首先可以发现这样的次数至多 1 次,因为操作了两次就把前一次删掉,显然不会更劣。容易发现操作的这一次位置应该尽量靠右。

Code

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

const int N = 1e6 + 5;

int n;
char s[N];
int sum[N], pmx[N], pmi[N];

int main() {
  scanf("%s", s + 1);
  n = strlen(s + 1);
  sum[0] = 0;
  pmx[0] = 0;
  pmi[0] = 0;
  for (int i = 1; i <= n; i++) {
    sum[i] = sum[i - 1] + 1 - 2 * (s[i] == '0');
    pmx[i] = max(pmx[i - 1], sum[i]);
    pmi[i] = min(pmi[i - 1], sum[i]);
  }
  int suf_mi = sum[n], suf_mx = sum[n];
  int ans = pmx[n] - pmi[n];
  for (int i = n; i; i--) {
    suf_mi = min(suf_mi, sum[i]);
    suf_mx = max(suf_mx, sum[i]);
    if (s[i] == '?') {
      if (pmx[i - 1] + 1 < suf_mx) {
        suf_mx -= 2;
        suf_mi -= 2;
        ans = min(ans, max(pmx[i - 1], suf_mx) - min(pmi[i - 1], suf_mi));
      }
    }
  }
  suf_mi = sum[n], suf_mx = sum[n];
  bool found = false;
  for (int i = n; i; i--) {
    suf_mi = min(suf_mi, sum[i]);
    suf_mx = max(suf_mx, sum[i]);
    if (s[i] == '?') {
      if (pmx[i - 1] + 1 == suf_mx) {
        ans = min(ans, max(pmx[i - 1], suf_mx - 2) - min(pmi[i - 1], suf_mi - 2));
        if (!found) {
          suf_mi -= 2;
          suf_mx -= 2;
          found = true;
        }
      } else if (pmx[i - 1] + 1 < suf_mx) {
        suf_mx -= 2;
        suf_mi -= 2;
        ans = min(ans, max(pmx[i - 1], suf_mx) - min(pmi[i - 1], suf_mi));
      }
    }
  }
  printf("%d\n", ans);
  return 0;
}

Problem C Range Set

  不妨设 $A > B$,显然对于每种局面存在一种方案使得至少使用一次 $A$ 操作。

  如果能够进行一次 $A$ 操作,从左到右填出最后一次左边的颜色,再从右到左填出右边的颜色即可。

  然后考虑计算一次 $A$ 操作都进行不了的方案数,这个等价于计算任意两个相邻的 1 的长度小于 $B$ 的连续段之间的长度小于 $A$。

  这个简单 dp 一下就好了。

Code

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

#define ll long long

void exgcd(int a, int b, int& x, int& y) {
  if (!b) {
    x = 1, y = 0;
  } else {
    exgcd(b, a % b, y, x);
    y -= (a / b) * x;
  }
}

int inv(int a, int n) {
  int x, y;
  exgcd(a, n, x, y);
  return (x < 0) ? (x + n) : (x);
}

const int Mod = 1e9 + 7;

template <const int Mod = :: Mod>
class Z {
  public:
    int v;

    Z() : v(0) {	}
    Z(int x) : v(x){	}
    Z(ll x) : v(x % Mod) {	}

    friend Z operator + (const Z& a, const Z& b) {
      int x;
      return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x));
    }
    friend Z operator - (const Z& a, const Z& b) {
      int x;
      return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x));
    }
    friend Z operator * (const Z& a, const Z& b) {
      return Z(a.v * 1ll * b.v);
    }
    friend Z operator ~(const Z& a) {
      return inv(a.v, Mod);
    }
    friend Z operator - (const Z& a) {
      return Z(0) - a;
    }
    Z& operator += (Z b) {
      return *this = *this + b;
    }
    Z& operator -= (Z b) {
      return *this = *this - b;
    }
    Z& operator *= (Z b) {
      return *this = *this * b;
    }
};

Z<> qpow(Z<> a, int p) {
  Z<> rt = Z<>(1), pa = a;
  for ( ; p; p >>= 1, pa = pa * pa) {
    if (p & 1) {
      rt = rt * pa;
    }
  }
  return rt;
}

typedef Z<> Zi;

const int N = 5005;

int n, A, B;
Zi f[N], g[N][2];

int main() {
  scanf("%d%d%d", &n, &A, &B);
  if (A > B) {
    swap(A, B);
  }
  f[0] = 1;
  for (int i = 1; i <= B; i++) {
    f[i] = f[i - 1];
    for (int j = A; j <= i; j++) {
      if (j == i) {
        f[i] += 1;
      } else {
        f[i] += f[i - j - 1];
      }
    }
  }
  g[0][0] = 1;
  for (int i = 1; i < B; i++) {
    g[i][0] = f[i - 1]; 
  }
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j < B && j <= i; j++) {
      g[i][0] += g[i - j][1] * ((j == 1) ? 1 : f[j - 2]);
    }
    for (int j = 1; j < A && j <= i; j++) {
      g[i][1] += g[i - j][0];
    }
  }
  Zi ans = g[n][1];
  for (int i = 1; i < B; i++) {
    ans += g[n - i][1] * f[i - 1];
  }
  ans = qpow(2, n) - ans;
  printf("%d\n", ans.v);
  return 0;
}

Problem D Lamps and Buttons

  排序排列对应的若干个环。显然每个灯属于的环必选包含前 $A$ 个中的某一个。

  考虑最优策略显然是:当还未全点亮时,选择一个点亮的灯,操作它, 如果它熄灭了就失败,否则显然可以点亮它所在的环。

  所以问题等价于计算满足满足下列条件的环排列划分的方案数:

  • 每个环都包含至少一个点亮的灯
  • 第一个自环后面的环只包含前 $A$ 个灯中的灯。

  考虑枚举第一个自环的位置,然后对前面每个环的大小都大于 1 进行容斥。

  首先计算出 $f_{i, j}$ 表示考虑前 $i$ 个灯,有 $j$ 个被硬点是自环的方案数。

  然后考虑怎么计算答案。注意到计算答案之和 $j$ 有关,设 $t_i$ 表示当有 $j$ 个灯被硬点为自环时,将剩下的灯加入环排列的方案数。

  那么考虑 $f_{i - 1, j}$ 贡献到答案的系数。枚举后面 $A - i$ 个灯有多少个插入前 $i$ 个的环排列中,那么有:

$$
\sum_{k=0}^{A-i}(i - j - 1)^{\overline{k}} \binom{A-i}{k} (A - i - k)! t_{j + 1 + m - i}
$$

  将组合数拆成阶乘表示的形式,然后提出 $(A - i)!$,然后设 $d = i - j,  l = m - i$,很容易在 $O(n^2)$ 的时间内预处理系数。

Code

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

#define ll long long

void exgcd(int a, int b, int& x, int& y) {
  if (!b) {
    x = 1, y = 0;
  } else {
    exgcd(b, a % b, y, x);
    y -= (a / b) * x;
  }
}

int inv(int a, int n) {
  int x, y;
  exgcd(a, n, x, y);
  return (x < 0) ? (x + n) : (x);
}

const int Mod = 1e9 + 7;

template <const int Mod = :: Mod>
class Z {
  public:
    int v;

    Z() : v(0) {	}
    Z(int x) : v(x){	}
    Z(ll x) : v(x % Mod) {	}

    friend Z operator + (const Z& a, const Z& b) {
      int x;
      return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x));
    }
    friend Z operator - (const Z& a, const Z& b) {
      int x;
      return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x));
    }
    friend Z operator * (const Z& a, const Z& b) {
      return Z(a.v * 1ll * b.v);
    }
    friend Z operator ~(const Z& a) {
      return inv(a.v, Mod);
    }
    friend Z operator - (const Z& a) {
      return Z(0) - a;
    }
    Z& operator += (Z b) {
      return *this = *this + b;
    }
    Z& operator -= (Z b) {
      return *this = *this - b;
    }
    Z& operator *= (Z b) {
      return *this = *this * b;
    }
};

Z<> qpow(Z<> a, int p) {
  Z<> rt = Z<>(1), pa = a;
  for ( ; p; p >>= 1, pa = pa * pa) {
    if (p & 1) {
      rt = rt * pa;
    }
  }
  return rt;
}

typedef Z<> Zi;

const int N = 1e7 + 5, M = 5005;

vector<Zi> fac, _fac;
Zi comb[M][M];

void init_fac(int n) {
  fac.resize(n + 1);
  _fac.resize(n + 1);
  fac[0] = 1;
  for (int i = 1; i <= n; i++) {
    fac[i] = fac[i - 1] * i;
  }
  _fac[n] = ~fac[n];
  for (int i = n; i; i--) {
    _fac[i - 1] = _fac[i] * i;
  }
}
void init(int n) {
  comb[0][0] = 1;
  for (int i = 1; i <= n; i++) {
    comb[i][0] = 1;
    for (int j = 1; j <= i; j++) {
      comb[i][j] = comb[i - 1][j - 1] + comb[i - 1][j];
    }
  }
}

int n, m;
Zi f[M][M], g[M][M], t[M];

int main() {
  scanf("%d%d", &n, &m);
  init_fac(n);
  init(m);

  for (int i = 0; i < m; i++) {
    t[i] = fac[n - 1 - i] * _fac[m - 1 - i];
  }
  for (int d = 1; d <= m; d++) {
    g[d][0] = t[m - d + 1];
    if (d == 1) {
      for (int j = 1; d + j <= m; j++) {
        g[d][j] = g[d][j - 1];
      }
    } else {
      for (int j = 1; d + j <= m; j++) {
        g[d][j] = g[d][j - 1] + fac[d + j - 2] * _fac[d - 2] * _fac[j] * t[m - d + 1 - j];
      }
    }
    for (int j = 1; d + j <= m; j++) {
      g[d][j] *= fac[j];
    }
  }
  f[0][0] = 1;
  Zi ans = 0;
  for (int i = 1; i <= m; i++) {
    f[i][0] = f[i - 1][0] * i;
    for (int j = 1; j <= i; j++) {
      f[i][j] = f[i - 1][j] * (i - j) - f[i - 1][j - 1];
    }
    for (int j = 0; j < i; j++) {
      ans += f[i - 1][j] * g[i - j][m - i];
    }
  }
  for (int i = 0; i < m; i++) {
    ans += f[m][i] * t[i];
  }
  printf("%d\n", ans.v);
  return 0;
}

Problem E Fragile Balls

  挖坑待填,明天再说.jpeg

Problem F Division into Multiples

  挖坑待填,明天再说.jpeg

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章