ABC127E. Cell Distance

(|xixj|+|yiyj|)=|xixj|+|yiyj|

所以,xy 可以分别单独考虑
这里仅讨论 x 的贡献,y 是类似的

所有的放法ij|xixj|=ij所有的放法|xixj|=ij|xixj|×nm2Ck2=nm2Ck2ij|xixj|=nm2Ck2d=0n1d×(nd)×m×m

代码实现
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using mint = modint1000000007;
struct modinv {
int n; vector<mint> d;
modinv(): n(2), d({0,1}) {}
mint operator()(int i) {
while (n <= i) d.push_back(-d[mint::mod()%n]*(mint::mod()/n)), ++n;
return d[i];
}
mint operator[](int i) const { return d[i];}
} invs;
struct modfact {
int n; vector<mint> d;
modfact(): n(2), d({1,1}) {}
mint operator()(int i) {
while (n <= i) d.push_back(d.back()*n), ++n;
return d[i];
}
mint operator[](int i) const { return d[i];}
} facts;
struct modfactinv {
int n; vector<mint> d;
modfactinv(): n(2), d({1,1}) {}
mint operator()(int i) {
while (n <= i) d.push_back(d.back()*invs(n)), ++n;
return d[i];
}
mint operator[](int i) const { return d[i];}
} ifacts;
mint comb(int n, int k) {
if (n < k || k < 0) return 0;
return facts(n)*ifacts(k)*ifacts(n-k);
}
int main() {
int n, m, k;
cin >> n >> m >> k;
mint ans;
rep(i, n) ans += mint(i)*(n-i)*m*m;
rep(j, m) ans += mint(j)*(m-j)*n*n;
ans *= comb(n*m-2, k-2);
cout << ans.val() << '\n';
return 0;
}

ABC149F. Surrounded Nodes

ABC223H. Xor Query

前缀线性基的板题
对于每一位上的基底,尽可能挂越靠右的数

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
const int D = 60;
template<typename T>
struct MaxBasis {
vector<T> d;
vector<int> w;
MaxBasis(): d(D), w(D) {}
void add(T x, int nw) {
rep(i, D) if (x>>i&1) {
if (d[i]) {
if (nw > w[i]) swap(d[i], x), swap(w[i], nw);
x ^= d[i];
}
else {
d[i] = x;
w[i] = nw;
break;
}
}
}
bool solve(int l, ll x) {
rep(i, D) if (x>>i&1) {
if (w[i] >= l) x ^= d[i];
}
return x == 0;
}
};
struct Q {
int i, l; ll x;
Q(int i, int l, ll x): i(i), l(l), x(x) {}
};
int main() {
cin.tie(nullptr) -> sync_with_stdio(false);
int n, q;
cin >> n >> q;
vector<ll> a(n);
rep(i, n) cin >> a[i];
vector<vector<Q>> qs(n);
rep(qi, q) {
int l, r; ll x;
cin >> l >> r >> x;
--l; --r;
qs[r].emplace_back(qi, l, x);
}
MaxBasis<ll> mb;
vector<bool> ans(q);
rep(i, n) {
mb.add(a[i], i);
for (auto [qi, l, x] : qs[i]) {
ans[qi] = mb.solve(l, x);
}
}
rep(i, q) {
if (ans[i]) puts("Yes");
else puts("No");
}
return 0;
}

ABC229G. Longest Y

双指针

先预处理出每个 Y 左边有多少个 .,对于一段区间将所有的 Y 交换到一起意味着让区间里每个 Y 处左边的 . 数相同,而 “让区间里每个 Y 处左边的 . 数相同”的最小操作次数是个经典问题,就是 LC462。然后预处理一下 . 数序列的前缀和就可以做到 O(1) 判定。

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
int main() {
string s; ll k;
cin >> s >> k;
int n = s.size();
vector<int> a;
rep(i, n) if (s[i] == 'Y') a.push_back(i-a.size());
n = a.size();
vector<ll> d(n+1);
rep(i, n) d[i+1] = d[i]+a[i];
int ans = 0;
int r = 0;
rep(l, n) {
while (r < n) {
int nr = r+1;
int c = (l+nr)/2;
ll now = ll(c-l)*a[c] - (d[c]-d[l]);
now += (d[nr]-d[c]) - ll(nr-c)*a[c];
if (now > k) break;
r = nr;
}
ans = max(ans, r-l);
}
cout << ans << '\n';
return 0;
}

ABC230F. Predilection

原题可以理解成对原序列划分成若干段,接着分别对每段求和,就得到了一个新的序列 B,问能生成多少种本质不同的序列 B

可以发现每次操作,原序列的前缀和序列 S 中都会少一个数(显然不会删掉 S0Sn),那么本题就转化成了有多少个本质不同的序列 (S1,S2,,Sn1) 的子序列,而这个问题是经典的dp题。

dp[i] 表示最后取的是数字 Si 的本质不同的子序列个数

转移方程:

dp[i]=ji1[Si 上一次出现的位置j] dp[j]

可以用 map 来维护 Si 上一次出现的位置

这样一来时间复杂度就是 O(n2)

下面考虑进行优化:

可以考虑容斥

dp[i+1]=dp[i]+(dp[i]dp[last[Si]])

代码实现
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
using mint = modint998244353;
int main() {
int n;
cin >> n;
vector<ll> a(n);
rep(i, n) cin >> a[i];
vector<ll> s(n+1);
rep(i, n) s[i+1] = s[i]+a[i];
s.erase(s.begin());
s.pop_back();
n--;
mint ans = 1;
map<ll, mint> dp;
rep(i, n) {
mint tmp = ans;
ans += ans-dp[s[i]];
dp[s[i]] = tmp;
}
cout << ans.val() << '\n';
return 0;
}

ABC230G. GCD Permutation

f(A) 表示从 A 中任选两个数不互素的二元组的个数
那么答案就是 k=2nf(Pki)μ(k),这里的 Pki 指由 P 中所有下标为 k 的倍数的数构成的子序列,μ(k)=μ(k)

f(A)=k=2nμ(k)×能被k整除的Ai

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
using P = pair<int, int>;
struct Sieve {
int n;
vector<int> f, primes;
Sieve(int n=1): n(n), f(n+1) {
f[0] = f[1] = -1;
for (ll i = 2; i <= n; ++i) {
if (f[i]) continue;
primes.push_back(i);
f[i] = i;
for (ll j = i*i; j <= n; j += i) {
if (!f[j]) f[j] = i;
}
}
}
vector<int> factorList(int x) {
vector<int> res;
while (x != 1) {
res.push_back(f[x]);
x /= f[x];
}
return res;
}
vector<P> factor(int x) {
vector<int> fl = factorList(x);
if (fl.size() == 0) return {};
vector<P> res(1, P(fl[0], 0));
for (int p : fl) {
if (res.back().first == p) {
res.back().second++;
}
else {
res.emplace_back(p, 1);
}
}
return res;
}
vector<int> divisors2(int x) {
auto ps = factor(x);
vector<int> res{1};
for (auto [p, _] : ps) {
for (int i = res.size()-1; i >= 0; --i) {
res.push_back(res[i]*p);
}
}
res.erase(res.begin());
return res;
}
};
ll c2(ll n) { return n*(n+1)/2; }
int main() {
int n;
cin >> n;
Sieve prime(n);
vector<int> a(n+1);
rep(i, n) cin >> a[i+1];
vector<int> mu(n+1, -1);
mu[1] = 0;
for (int p : prime.primes) {
for (int i = p; i <= n; i += p) mu[i] = -mu[i];
for (ll i = (ll)p*p; i <= n; i += (ll)p*p) mu[i] = 0;
}
vector<vector<int>> ds(n+1);
for (int i = 1; i <= n; ++i) {
ds[i] = prime.divisors2(i);
}
ll ans = 0;
for (int k = 2; k <= n; ++k) {
if (mu[k] == 0) continue;
ll now = 0;
unordered_map<int, int> s;
for (int i = k; i <= n; i += k) {
for (int x : ds[a[i]]) s[x]++;
}
for (auto [j, cnt] : s) now += c2(cnt)*mu[j];
ans += now*mu[k];
}
cout << ans << '\n';
return 0;
}

ABC233F. Swap and Sort

原题其实就是给定一张 N 个点 M 条边的图,顶点 i 上放有棋子 Pi 。一条边直接相连的两端点可以交换其上放置的棋子。问是否能让棋子 pi 落在点 i 上。对于树的情况比较简单,可以从叶子节点 V 开始搜,在整棵树上找是否有哪个点上放有棋子 v,如果找到了的话,那么这个叶节点及其和它父亲相连的边就可以不考虑了。那么对于一般图的话,只需考虑每个连通块的生成树即可。

代码实现
#include <bits/stdc++.h>
#if __has_include(<atcoder/all>)
#include <atcoder/all>
using namespace atcoder;
#endif
using namespace std;
#define rep(i, n) for (int i = 0; i < (n); ++i)
struct Edge {
int to, id;
Edge(int to, int id): to(to), id(id) {}
};
int main() {
int n;
cin >> n;
vector<int> P(n);
rep(i, n) cin >> P[i];
rep(i, n) P[i]--;
int m;
cin >> m;
dsu uf(n);
vector<vector<Edge>> g(n);
rep(i, m) {
int a, b;
cin >> a >> b;
--a; --b;
if (uf.same(a, b)) continue;
uf.merge(a, b);
g[a].emplace_back(b, i+1);
g[b].emplace_back(a, i+1);
}
vector<int> ans;
rep(sv, n) if (uf.leader(sv) == sv) {
auto get = [&](auto& f, int v, int tg, int p=-1) -> bool {
if (P[v] == tg) return true;
for (auto [u, id] : g[v]) {
if (u == p) continue;
if (f(f, u, tg, v)) {
ans.push_back(id);
swap(P[v], P[u]);
return true;
}
}
return false;
};
auto dfs = [&](auto& f, int v, int p=-1) -> void {
for (auto [u, id] : g[v]) {
if (u == p) continue;
f(f, u, v);
}
if (!get(get, v, v)) {
puts("-1");
exit(0);
}
};
dfs(dfs, sv);
}
cout << ans.size() << '\n';
for (int i : ans) cout << i << ' ';
return 0;
}

ABC233G. Strongest Takahashi

注意到如果能用一个子矩阵,用两个相交的子矩阵反而更劣
考虑二维区间dp
dp[si][sj][ti][tj] 表示摧毁左上坐标为 (si,sj) 以及右下坐标为 (ti1,tj1) 的子矩阵中的所有障碍物的最小代价

代码实现
#include<bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
inline void chmin(int& x, int y) { if (x > y) x = y; }
int dp[55][55][55][55];
int main() {
int n;
cin >> n;
vector<string> s(n);
rep(i, n) cin >> s[i];
rep(ti, n+1)rep(si, ti)rep(tj, n+1)rep(sj, tj) {
dp[si][sj][ti][tj] = max(ti-si, tj-sj);
}
rep(i, n)rep(j, n) if (s[i][j] == '.') {
dp[i][j][i+1][j+1] = 0;
}
for (int wi = 1; wi <= n; ++wi) {
for (int wj = 1; wj <= n; ++wj) {
rep(si, n)rep(sj, n) {
int ti = si+wi, tj = sj+wj;
if (ti > n) break;
if (tj > n) break;
for (int k = si+1; k < ti; ++k) {
int now = dp[si][sj][k][tj];
now += dp[k][sj][ti][tj];
chmin(dp[si][sj][ti][tj], now);
}
for (int k = sj+1; k < tj; ++k) {
int now = dp[si][sj][ti][k];
now += dp[si][k][ti][tj];
chmin(dp[si][sj][ti][tj], now);
}
}
}
}
cout << dp[0][0][n][n] << '\n';
return 0;
}

ABC233H. Manhattan Christmas Tree

整体二分
先将坐标系顺时针旋转45°,得到切比雪夫距离,接下来就是经典的二维数点问题了

代码实现
#include <bits/stdc++.h>
#if __has_include(<atcoder/all>)
#include <atcoder/all>
using namespace atcoder;
#endif
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using P = pair<int, int>;
const int M = 100005;
const int MX = M*2;
int main() {
int n;
cin >> n;
vector<vector<int>> ps(MX);
rep(i, n) {
int x, y;
cin >> x >> y;
ps[x+y].push_back(x-y+M);
}
int q;
cin >> q;
vector<int> a(q), b(q), k(q);
rep(i, q) {
int x, y;
cin >> x >> y >> k[i];
a[i] = x+y;
b[i] = x-y+M;
}
vector<int> wa(q, -1), ac(q, MX);
rep(ti, 18) {
vector<int> num(q), wj(q);
vector<vector<int>> qs(MX);
rep(i, q) {
wj[i] = (wa[i]+ac[i])/2;
int lx = a[i]-wj[i], rx = a[i]+wj[i]+1;
lx = max(lx, 0);
rx = min(rx, MX-1);
qs[lx].push_back(i);
qs[rx].push_back(q+i);
}
fenwick_tree<int> d(MX);
rep(x, MX) {
for (int qi : qs[x]) {
int i = qi%q;
int sign = qi < q ? -1 : 1;
int ly = b[i]-wj[i], ry = b[i]+wj[i]+1;
ly = max(ly, 0);
ry = min(ry, MX);
num[i] += d.sum(ly, ry)*sign;
}
for (int y : ps[x]) d.add(y, 1);
}
rep(i, q) {
if (num[i] >= k[i]) ac[i] = wj[i];
else wa[i] = wj[i];
}
}
rep(i, q) cout << ac[i] << '\n';
return 0;
}

ABC235G. Gardens

先考虑条件1,就是个简单的容斥,见 盒子与球

再结合条件2,假设恰有 M 个花园对条件1没有限制,方案数就是

f(M)=i=0aMCi×i=0bMCi×i=0cMCi

那么最终答案就是

j=0N(1)NjNCjf(j)

这样时间复杂度是 O(N2)

考虑对组合数的前缀和进行加速

fa(M)=i=0aMCi

有递推公式:fa(M+1)=2fa(M)MCa

那么我们就可以 O(N) 预处理出 fa(0)fa(N)

代码实现
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using mint = modint998244353;
struct modinv {
int n; vector<mint> d;
modinv(): n(2), d({0,1}) {}
mint operator()(int i) {
while (n <= i) d.push_back(-d[mint::mod()%n]*(mint::mod()/n)), ++n;
return d[i];
}
mint operator[](int i) const { return d[i];}
} invs;
struct modfact {
int n; vector<mint> d;
modfact(): n(2), d({1,1}) {}
mint operator()(int i) {
while (n <= i) d.push_back(d.back()*n), ++n;
return d[i];
}
mint operator[](int i) const { return d[i];}
} facts;
struct modfactinv {
int n; vector<mint> d;
modfactinv(): n(2), d({1,1}) {}
mint operator()(int i) {
while (n <= i) d.push_back(d.back()*invs(n)), ++n;
return d[i];
}
mint operator[](int i) const { return d[i];}
} ifacts;
mint comb(int n, int k) {
if (n < k || k < 0) return 0;
return facts(n)*ifacts(k)*ifacts(n-k);
}
int main() {
int n;
cin >> n;
vector<int> a(3);
rep(i, 3) cin >> a[i];
vector f(3, vector<mint>(n+1));
rep(i, 3) {
f[i][0] = 1;
rep(j, n) {
f[i][j+1] = f[i][j]*2;
if (j >= a[i]) f[i][j+1] -= comb(j, a[i]);
}
}
mint ans;
rep(j, n+1) {
mint now = 1;
rep(i, 3) now *= f[i][j];
now *= comb(n, j);
ans = now-ans;
}
cout << ans.val() << '\n';
return 0;
}

ABC249F. Ignore Operations

考虑枚举最后一个操作 1,那么前面的操作不管是啥样都无所谓了,我们只需考虑后面的操作,显然应该将后面的所有操作 1 都跳过,为了使得最终的结果最大,所以应该尽可能跳过操作 2y<0 的操作。可以用小根堆来维护所有 y<0 的操作 2,如果当前堆的大小超过剩下的 k,就弹出堆中的最大值。 对于枚举最后一个操作 1 可以倒着枚举所有操作

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
int main() {
int n, k;
cin >> n >> k;
vector<int> t(n), y(n);
rep(i, n) cin >> t[i] >> y[i];
ll ans = -1e18;
ll sum = 0;
priority_queue<int> q;
for (int i = n-1; i >= 0; --i) {
if (k < 0) break;
if (t[i] == 1) {
ans = max(ans, y[i]+sum);
k--;
if (q.size() > k) {
sum += q.top(); q.pop();
}
}
else {
if (y[i] >= 0) sum += y[i];
else {
q.push(y[i]);
if (q.size() > k) {
sum += q.top(); q.pop();
}
}
}
}
if (k >= 0) ans = max(ans, sum);
cout << ans << '\n';
return 0;
}

ABC239Ex. Dice Product 2

dp[i] 表示由 M 通过除以若干个整数 x 变成 i 的期望次数

转移方程:dp[i]=1+j=1ndp[ij]dp[i]=NN1(1+j=2ndp[ij])

时间复杂度为 O(NM)

注意到和 ij 一样的部分可以用整除分块来优化
用记忆化搜索实现,复杂度同杜教筛,为 O(m34)

代码实现
#include <bits/stdc++.h>
#if __has_include(<atcoder/all>)
#include <atcoder/all>
using namespace atcoder;
#endif
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using mint = modint1000000007;
int main() {
int n, m;
cin >> n >> m;
mint inv_n1 = mint(n-1).inv();
mint cost = mint(n)/(n-1);
unordered_map<int, mint> dp;
auto f = [&](auto& f, int x) -> mint {
if (x == 0) return 0;
if (dp.count(x)) return dp[x];
mint res;
for (int i = n; i > 1;) {
int a = x/i;
int ni = x/(a+1);
res += f(f, a)*(i-ni);
i = ni;
}
res *= inv_n1; res += cost;
return dp[x] = res;
};
mint ans = f(f, m);
cout << ans.val() << '\n';
return 0;
}

ABC265G. 012 Inversion

延迟线段树

每个点需要维护以下信息:

  • 0/1/2 的个数
  • 有序对 (0,0), (0,1), (0,2)(1,0)(1,1)(1,2)(2,0)(2,1)(2,2) 的个数

对于操作二其实就是延迟线段树里的映射 F

代码实现
#include <bits/stdc++.h>
#if __has_include(<atcoder/all>)
#include <atcoder/all>
using namespace atcoder;
#endif
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
struct S {
array<int, 3> c;
array<array<ll, 3>, 3> d;
S() {
fill(c.begin(), c.end(), 0);
rep(i, 3)rep(j, 3) d[i][j] = 0;
}
};
S op(S a, S b) {
rep(i, 3)rep(j, 3) {
a.d[i][j] += b.d[i][j];
a.d[i][j] += (ll)a.c[i]*b.c[j];
}
rep(i, 3) a.c[i] += b.c[i];
return a;
}
S e() { return S(); }
struct F {
array<int, 3> a;
F(): a({0, 1, 2}) {}
};
S mapping(F f, S x) {
S res;
rep(i, 3)rep(j, 3) {
res.d[f.a[i]][f.a[j]] += x.d[i][j];
}
rep(i, 3) res.c[f.a[i]] += x.c[i];
return res;
}
F comp(F f2, F f1) {
rep(i, 3) f1.a[i] = f2.a[f1.a[i]];
return f1;
}
F id() { return F(); }
int main() {
cin.tie(nullptr) -> sync_with_stdio(false);
int n, q;
cin >> n >> q;
vector<int> a(n);
rep(i, n) cin >> a[i];
lazy_segtree<S, op, e, F, mapping, comp, id> t(n);
rep(i, n) {
S s;
s.c[a[i]] = 1;
t.set(i, s);
}
rep(qi, q) {
int type, l, r;
cin >> type >> l >> r;
--l;
if (type == 1) {
S s = t.prod(l, r);
ll ans = s.d[1][0] + s.d[2][0] + s.d[2][1];
cout << ans << '\n';
}
else {
F f;
rep(i, 3) cin >> f.a[i];
t.apply(l, r, f);
}
}
return 0;
}