Atcoder Grand Contest 031 简要题解

Colorful Subsequence

对于每种字符,其要么不选,要么选择一个,所以答案是 ∏ ( c n t i − 1 ) − 1 \prod (cnt_i-1)-1 (cnti1)1

#include 

using namespace std;

const int md = (int) 1e9 + 7;

inline int mul(int x, int y) {
  return (int) ((long long) x * y % md);
}

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n;
  string s;
  cin >> n >> s;
  vector<int> cnt(26);
  for (auto c : s) {
    ++cnt[c - 'a'];
  }
  int ans = 1;
  for (auto x : cnt) {
    ans = mul(ans, x + 1);
  }
  cout << ans - 1 << "\n";
  return 0;
}

Reversi

d p i dp_i dpi 表示考虑前 i i i 个的答案,如果 a i = a i − 1 a_i = a_{i-1} ai=ai1 则对 i i i 操作和 i − 1 i-1 i1 操作等价;否则可以从 a i = a j + 1 , a i ≠ a j a_i = a_{j+1}, a_i\neq a_j ai=aj+1,ai̸=aj 的位置转移过来,对于每种不同的 a a a 记录 d p dp dp 值之和即可。

#include 

using namespace std;

const int md = (int) 1e9 + 7;

inline void add(int &x, int y) {
  x += y;
  if (x >= md) {
    x -= md;
  }
}

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n;
  cin >> n;
  vector<int> a(n);
  for (int i = 0; i < n; ++i) {
    cin >> a[i];
  }
  int m = *max_element(a.begin(), a.end()) + 1;
  vector<int> sum(m);
  vector<int> dp(n + 1);
  dp[0] = 1;
  for (int i = 0; i < n; ++i) {
    dp[i + 1] = dp[i];
    if (!i || a[i] != a[i - 1]) {
      add(dp[i + 1], sum[a[i]]);
    }
    if (!i || a[i] != a[i - 1]) {
      add(sum[a[i]], dp[i]);
    }
  }
  cout << dp[n] << "\n";
  return 0;
}

Differ by 1 Bit

将所有数都异或上 a a a ,则原问题等价于起点是 0 0 0 。由于位的顺序不影响答案,所以可以认为终点是 2 k − 1 2^k-1 2k1 。当 k k k 是偶数的时候,考虑奇偶性不难发现无解。否则考虑对于 k k k 是奇数构造 0 → 2 k − 1 0\to 2^k-1 02k1 ,以及对于任意 k k k 构造 0 → 1 0\to 1 01 。第二部分很简单,用 k − 1 k-1 k1 来归纳构造即可;第一部分则考虑用 k − 2 k-2 k2 归纳构造:

00000000
........(apply 0->2^x-1 for x=k-2)
11111100
11111110
........(apply 0->1 for x=k-2)
01111110
01111111
........(apply 0->1 for x=k-1)
11111111

之后,让后 k k k 位从 0 0 0 走到 2 k − 1 2^k-1 2k1 ,每走一步前 n − k n-k nk 位就走一次 0 → 1 0\to 1 01 的构造即可。

#include 

using namespace std;

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n, a, b;
  cin >> n >> a >> b;
  int k = __builtin_popcount(a ^ b);
  if (k % 2 == 0) {
    cout << "NO" << "\n";
    return 0;
  }
  cout << "YES" << "\n";
  vector<vector<int>> to_one(n + 1);
  to_one[0] = {0};
  to_one[1] = {0, 1};
  for (int i = 2; i <= n; ++i) {
    for (auto x : to_one[i - 1]) {
      to_one[i].push_back(x << 1);
    }
    reverse(to_one[i - 1].begin(), to_one[i - 1].end());
    for (auto x : to_one[i - 1]) {
      to_one[i].push_back(x << 1 | 1);
    }
    reverse(to_one[i - 1].begin(), to_one[i - 1].end());
  }
  vector<vector<int>> to_all(k + 1);
  to_all[1] = {0, 1};
  for (int i = 3; i <= k; i += 2) {
    for (auto x : to_all[i - 2]) {
      to_all[i].push_back(x);
    }
    for (auto x : to_one[i - 2]) {
      to_all[i].push_back(x ^ ((1 << (i - 1)) - 1));
    }
    for (auto x : to_one[i - 1]) {
      to_all[i].push_back(x ^ ((1 << i) - 2));
    }
  }
  vector<int> ans;
  for (auto x : to_all[k]) {
    for (auto y : to_one[n - k]) {
      ans.push_back(y << k | x);
    }
    reverse(to_one[n - k].begin(), to_one[n - k].end());
  }
  vector<int> to_bit(n);
  int ptr = 0;
  for (int i = 0; i < n; ++i) {
    if ((a ^ b) >> i & 1) {
      to_bit[ptr++] = i;
    }
  }
  for (int i = 0; i < n; ++i) {
    if (!((a ^ b) >> i & 1)) {
      to_bit[ptr++] = i;
    }
  }
  for (int i = 0; i < 1 << n; ++i) {
    int real_ans = 0;
    for (int j = 0; j < n; ++j) {
      if (ans[i] >> j & 1) {
        real_ans |= 1 << to_bit[j];
      }
    }
    ans[i] = real_ans;
  }
  for (int i = 0; i < 1 << n; ++i) {
    if (i) {
      cout << " ";
    }
    cout << (ans[i] ^ a);
  }
  cout << "\n";
  return 0;
}

A Sequence of Permutations

不难发现 f ( p , q ) = q × p − 1 f(p, q) = q\times p^{-1} f(p,q)=q×p1找规律 归纳可得:

$a_m = (q{-1}pqp{-1})^{\lfloor \frac{(m-2)}{6}\rfloor} \times f[(m-2)\bmod6] \times (pq{-1}p{-1}q)^{\lfloor \frac{(m-2)}{6}\rfloor} $

其中 f [ i ] f[i] f[i] 是一个和 p , q p, q p,q 有关的式子,是 O ( 1 ) O(1) O(1) 的。

#include 

using namespace std;

vector<int>& operator *= (vector<int> &p, vector<int> q) {
  vector<int> r = p;
  for (int i = 0; i < (int) p.size(); ++i) {
    p[i] = q[r[i]];
  }
  return p;
}

vector<int> operator * (vector<int> p, vector<int> q) {
  return p *= q;
}

vector<int> inv(const vector<int> &p) {
  int n = (int) p.size();
  vector<int> res(n);
  for (int i = 0; i < n; ++i) {
    res[p[i]] = i;
  }
  return res;
}

vector<int> power(vector<int> x, int y) {
  int n = (int) x.size();
  vector<int> res(n);
  for (int i = 0; i < n; ++i) {
    res[i] = i;
  }
  while (y) {
    if (y & 1) {
      res *= x;
    }
    x *= x;
    y >>= 1;
  }
  return res;
}

void output(vector<int> ans) {
  for (int i = 0; i < (int) ans.size(); ++i) {
    if (i) {
      cout << " ";
    }
    cout << ans[i] + 1;
  }
  cout << "\n";
}

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n, m;
  cin >> n >> m;
  vector<int> p(n);
  for (int i = 0; i < n; ++i) {
    cin >> p[i];
    --p[i];
  }
  vector<int> q(n);
  for (int i = 0; i < n; ++i) {
    cin >> q[i];
    --q[i];
  }
  if (m == 1) {
    output(p);
  } else if (m == 2) {
    output(q);
  } else {
    m -= 2;
    vector<int> inv_p = inv(p);
    vector<int> inv_q = inv(q);
    vector<int> ans(n);
    for (int i = 0; i < n; ++i) {
      ans[i] = i;
    }
    ans *= power(inv_q * p * q * inv_p, m / 6);
    switch (m % 6) {
      case 0:
        ans *= q;
        break;
      case 1:
        ans *= inv_p * q;
        break;
      case 2:
        ans *= inv_q * inv_p * q;
        break;
      case 3:
        ans *= inv_q * p * inv_q * inv_p * q;
        break;
      case 4:
        ans *= inv_q * p * p * inv_q * inv_p * q;
        break;
      case 5:
        ans *= inv_q * p * q * p * inv_q * inv_p * q;
        break;
    }
    ans *= power(p * inv_q * inv_p * q, m / 6);
    output(ans);
  }
  return 0;
}

Snuke the Phantom Thief

枚举最后选了多少个,这样限制就变成了某个前缀最多或最少选若干个,搞个上下界费用流就行了。

#include 

using namespace std;

using cap_t = int;
using cost_t = long long;

const cap_t cap_inf = 0x3f3f3f3f;
const cost_t cost_inf = 1ll << 60;

namespace flow {
struct edge {
  int to, rev;
  cost_t cost;
  cap_t cap;

  edge(int t, cap_t c, cost_t w, int r) {
    to = t;
    rev = r;
    cap = c;
    cost = w;
  }
};

vector<vector<edge>> adj;
vector<cost_t> dist;
int n, source, sink;
vector<bool> visit;
vector<int> cur;
cost_t res;
cap_t ans;

void init(int v, int s, int t) {
  n = v;
  ans = res = 0;
  source = s;
  sink = t;
  adj.clear();
  adj.resize(n);
  cur.resize(n);
  dist.resize(n);
  visit.resize(n);
}

void add(int x, int y, cap_t c, cost_t w) {
  adj[x].emplace_back(y, c, w, adj[y].size());
  adj[y].emplace_back(x, 0, -w, adj[x].size() - 1);
}

bool spfa() {
  queue<int> q;
  for (int i = 0; i < n; ++i) {
    dist[i] = cost_inf;
    visit[i] = false;
    cur[i] = 0;
  }
  dist[source] = 0;
  q.push(source);
  while (!q.empty()) {
    int x = q.front();
    q.pop();
    visit[x] = false;
    for (auto e : adj[x]) {
      if (e.cap && dist[e.to] > dist[x] + e.cost) {
        dist[e.to] = dist[x] + e.cost;
        if (!visit[e.to]) {
          visit[e.to] = true;
          q.push(e.to);
        }
      }
    }
  }
  return dist[sink] != cost_inf;
}

cap_t dfs(int x, cap_t f) {
  if (x == sink) {
    return f;
  }
  visit[x] = true;
  cap_t res = 0;
  for (int &i = cur[x]; i < (int) adj[x].size(); ++i) {
    edge &e = adj[x][i];
    if (e.cap && dist[e.to] == dist[x] + e.cost && !visit[e.to]) {
      cap_t w = dfs(e.to, min(e.cap, f - res));
      res += w;
      e.cap -= w;
      adj[e.to][e.rev].cap += w;
      if (res == f) {
        visit[x] = false;
        return res;
      }
    }
  }
  dist[x] = cost_inf;
  return res;
}

pair<cap_t, cost_t> min_cost_max_flow() {
  while (spfa()) {
    cap_t flow = dfs(source, cap_inf);
    ans += flow;
    res += flow * dist[sink];
  }
  return make_pair(ans, res);
}
}

using flow::source;
using flow::sink;
using flow::init;
using flow::add;
using flow::min_cost_max_flow;

const int MAX = 100;

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n;
  cin >> n;
  vector<int> x(n), y(n);
  vector<long long> z(n);
  for (int i = 0; i < n; ++i) {
    cin >> x[i] >> y[i] >> z[i];
  }
  vector<int> l(MAX, n), r(MAX, n), d(MAX, n), u(MAX, n);
  int m;
  cin >> m;
  while (m--) {
    string type;
    int a, b;
    cin >> type >> a >> b;
    --a;
    if (type == "L") {
      l[a] = b;
    } else if (type == "R") {
      r[a] = b;
    } else if (type == "D") {
      d[a] = b;
    } else {
      u[a] = b;
    }
  }
  for (int i = MAX - 2; ~i; --i) {
    l[i] = min(l[i], l[i + 1]);
    d[i] = min(d[i], d[i + 1]);
  }
  for (int i = 1; i < MAX; ++i) {
    r[i] = min(r[i], r[i - 1]);
    u[i] = min(u[i], u[i - 1]);
  }
  long long ans = 0;
  auto solve = [&](int take) {
    init(MAX * 2 + 4, MAX * 2 + 2, MAX * 2 + 3);
    add(source, 0, take, 0);
    add(MAX + 1, sink, take, 0);
    int need = take;
    for (int i = 0; i < MAX; ++i) {
      int low = max(0, take - l[max(i - 1, 0)]);
      int high = r[i];
      if (low > high) {
        return;
      }
      add(i, i + 1, high - low, 0);
      add(source, i + 1, low, 0);
      add(i, sink, low, 0);
      need += low;
    }
    for (int i = 0; i < MAX; ++i) {
      int low = max(0, take - d[max(i - 1, 0)]);
      int high = u[i];
      if (low > high) {
        return;
      }
      add(MAX + i + 2, MAX + i + 1, high - low, 0);
      add(source, MAX + i + 1, low, 0);
      add(MAX + i + 2, sink, low, 0);
      need += low;
    }
    for (int i = 0; i < n; ++i) {
      add(x[i], MAX + y[i] + 1, 1, -z[i]);
    }
    pair<int, long long> res = min_cost_max_flow();
    if (res.first == need) {
      ans = max(ans, -res.second);
    }
  };
  for (int i = 1; i <= n; ++i) {
    solve(i);
  }
  cout << ans << "\n";
  return 0;
}

Walk on Graph

倒过来考虑这个问题,走一条边 c c c 就是 x → 2 x + c x\to 2x+c x2x+c ,因为模数是奇数,所以 2 2 2 有逆元,即这是一个双射,所以一定能回到自己。

注意到一个点走两次一条边会回到自己, x → 4 x + 3 c x\to 4x+3c x4x+3c ,那么如果有另一条边 c ′ c' c ,则我们能凑出 3 ( c − c ′ ) 3(c-c') 3(cc) 。记 g g g 是所有边权之差以及模数的 gcd ⁡ \gcd gcd ,那么我们只需要在模 gcd ⁡ ( 3 g , M O D ) \gcd(3g, MOD) gcd(3g,MOD) 意义下考虑这个问题即可(此时模数要么是 g g g 要么是 3 g 3g 3g )。

假设所有边模 g g g 都为 z z z ,那么令 x ′ = x + z x'=x+z x=x+z ,将边权减去 z z z ,走一步后新的 x ′ x' x 会变成 2 ( x ′ − z ) + ( c + z ) + z = 2 x ′ + c 2(x'-z) + (c+z) + z = 2x' + c 2(xz)+(c+z)+z=2x+c ,和原来的规则一样,所以我们可以认为在新的图中,每条边都是 g g g 的倍数。

对于 ( 1 , x ) (1,x) (1,x) ,其能到的状态可以写成 ( v , 2 a x + p g ) ( 0 ≤ p < 3 ) (v, 2^ax+pg)(0\le p<3) (v,2ax+pg)(0p<3) 的形式,由之前的变换 x → 4 x + 3 c x\to 4x+3c x4x+3c ,且 c c c g g g 的倍数,得 x x x 4 x 4x 4x 是等价状态,故 0 ≤ a < 2 0\le a<2 0a<2 。这样新的图中点数就只有 O ( n ) O(n) O(n) 个了,用并查集连一下就好了。

#include 

using namespace std;

class dsu {
 public:
  vector<int> p;
  int n;

  dsu(int n): n(n) {
    p.resize(n);
    for (int i = 0; i < n; ++i) {
      p[i] = i;
    }
  }

  inline int find(int x) {
    while (x != p[x]) {
      x = p[x] = p[p[x]];
    }
    return x;
  }

  inline bool unite(int x, int y) {
    x = find(x);
    y = find(y);
    if (x == y) {
      return false;
    } else {
      p[x] = y;
      return true;
    }
  }
};

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n, m, q, md;
  cin >> n >> m >> q >> md;
  int g = md;
  vector<int> from(m), to(m), cost(m);
  for (int i = 0; i < m; ++i) {
    cin >> from[i] >> to[i] >> cost[i];
    --from[i];
    --to[i];
    g = __gcd(g, abs(cost[i] - cost[0]));
  }
  if ((md / g) % 3 == 0) {
    md = g * 3;
  } else {
    md = g;
  }
  int z = cost[0] % g;
  dsu p(n * 6);
  for (int i = 0; i < m; ++i) {
    int w = (cost[i] - z) / g % 3;
    for (int j = 0; j < 3; ++j) {
      p.unite(from[i] * 6 + j * 2 + 0, to[i] * 6 + (2 * j + w) % 3 * 2 + 1);
      p.unite(from[i] * 6 + j * 2 + 1, to[i] * 6 + (2 * j + w) % 3 * 2 + 0);
      p.unite(to[i] * 6 + j * 2 + 0, from[i] * 6 + (2 * j + w) % 3 * 2 + 1);
      p.unite(to[i] * 6 + j * 2 + 1, from[i] * 6 + (2 * j + w) % 3 * 2 + 0);
    }
  }
  vector<vector<bool>> can(2, vector<bool>(md));
  for (int i = 0, j = z; i < md * 2; ++i, j = j * 2 % md) {
    can[i & 1][j] = true;
  }
  while (q--) {
    int s, t, r;
    cin >> s >> t >> r;
    --s;
    --t;
    bool ans = false;
    for (int i = 0; i < 3; ++i) {
      for (int j = 0; j < 2; ++j) {
        if (p.find(t * 6) == p.find(s * 6 + i * 2 + j)) {
          if (can[j][(r + z + (3 - i) * g) % md]) {
            ans = true;
          }
        }
      }
    }
    cout << (ans ? "YES" : "NO") << "\n";
  }
  return 0;
}

你可能感兴趣的:(Atcoder Grand Contest 031 简要题解)