C. Odd One Subsequence
开个桶来统计每种数的个数,答案就是 \(\sum \binom{cnt[x]}{2} \times (n-cnt[x])\)
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
ll c2(ll n) {
return n*(n-1)/2;
}
int main() {
int n;
cin >> n;
vector<int> a(n);
rep(i, n) cin >> a[i], a[i]--;
vector<int> cnt(n);
rep(i, n) cnt[a[i]]++;
ll ans = 0;
rep(i, n) {
ans += c2(cnt[i])*(n-cnt[i]);
}
cout << ans << '\n';
return 0;
}
D. On AtCoder Conference
先把圆环上有人的点聚合并按位置升序展开成两圈线性序列,然后用双指针在这个线性序列上对每个“第一个遇到的人点”计算其覆盖的起点段长度乘以“从该点起遇到 \(\geqslant C\) 人的总数”,逐段累加得到 \(\sum X_i\) 。
代码实现
#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, c;
ll m;
cin >> n >> m >> c;
vector<ll> a(n);
rep(i, n) cin >> a[i];
map<ll, int> cnt;
rep(i, n) cnt[a[i]]++;
vector<pair<ll, int>> d;
for (auto p : cnt) d.emplace_back(p);
for (auto p : cnt) d.emplace_back(p.first+m, p.second);
int dn = cnt.size();
ll px = d[dn-1].first-m;
int r = 0, now = 0;
ll ans = 0;
rep(l, dn) {
while (now < c) {
now += d[r].second;
r++;
}
auto [x, num] = d[l];
ans += (x-px)*now;
px = x;
now -= num;
}
cout << ans << '\n';
return 0;
}
E. Hit and Away
以所有安全点为起点跑多源bfs,求出到其他点的 \(\mathrm{top}2\) 距离及其对应的源点
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using P = pair<int, int>;
int main() {
int n, m;
cin >> n >> m;
vector<vector<int>> to(n);
rep(i, m) {
int a, b;
cin >> a >> b;
--a; --b;
to[a].push_back(b);
to[b].push_back(a);
}
string s;
cin >> s;
vector<vector<P>> dist(n);
queue<tuple<int, int, int>> q;
auto push = [&](int v, int sv, int d) {
if (dist[v].size() >= 2) return;
for (auto p : dist[v]) if (p.second == sv) return;
dist[v].emplace_back(d, sv);
q.emplace(v, sv, d);
};
rep(v, n) if (s[v] == 'S') push(v, v, 0);
while (q.size()) {
auto [v, sv, d] = q.front(); q.pop();
for (int u : to[v]) push(u, sv, d+1);
}
rep(v, n) if (s[v] == 'D') {
int ans = dist[v][0].first + dist[v][1].first;
cout << ans << '\n';
}
return 0;
}
F. Shortest Path Query
热带半环+线段树
将每一列看成是一维序列上的一个点,用线段树维护三维矩阵的 \((\min, +)\) 运算,只需求出从左上角走到右下角在纵向上的最小步数,然后加上横向上固定的 \(n-1\) 步,就是最后的答案。
代码实现
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
const int INF = 1001001001;
using A = array<int, 3>;
using S = array<A, 3>;
const S inf = { A({INF, INF, INF}), A({INF, INF, INF}), A({INF, INF, INF}) };
S op(S a, S b) {
S c = inf;
rep(i, 3)rep(j, 3)rep(k, 3) c[i][k] = min(c[i][k], a[i][j]+b[j][k]);
return c;
}
S e() {
return S({A({0, INF, INF}), A({INF, 0, INF}), A({INF, INF, 0})});
}
S toS(const string& s) {
S a = inf;
rep(i, 3)rep(j, 3) a[i][j] = abs(i-j);
rep(k, 3) if (s[k] == '#') {
rep(i, k+1) for (int j = k; j < 3; ++j) {
a[i][j] = a[j][i] = INF;
}
}
return a;
}
int main() {
int n;
cin >> n;
vector<string> s(n, "...");
{
vector<string> S(3);
rep(i, 3) cin >> S[i];
rep(i, 3)rep(j, n) s[j][i] = S[i][j];
}
segtree<S, op, e> t(n);
rep(i, n) t.set(i, toS(s[i]));
int q;
cin >> q;
rep(qi, q) {
int r, c;
cin >> r >> c;
--r; --c;
s[c][r] ^= '.'^'#';
t.set(c, toS(s[c]));
S a = t.all_prod();
int ans = a[0][2] + n-1;
if (ans >= INF) ans = -1;
cout << ans << '\n';
}
return 0;
}
G. Sum of Pow of Mod of Linear
考虑把 \(\{ (Ak+B) \bmod M \big| k = 0, 1, 2, \cdots, N-1\}\) 这 \(N\) 个数,划分成若干个等差数列。
对于一个首项为 \(d_0\),公差为 \(d_1\),长为 \(n\) 的等差数列,其对答案的贡献为 \(\sum\limits_{i=0}^{n-1} X^{d_1i + d_0}\) 。这个是可以用类似快速幂的方法在 \(O(\log n)\) 时间内计算的。
因此,现在的问题是如何把上面那个东西分解成尽可能少的等差数列。事实上,有一种经典方法是这样的:
- 找一个参数 \(d\),然后计算 \(h = Ad \bmod M\) 。
- 把 \(0, 1, \cdots, N-1\) 这些下标先按 \(\bmod M\) 分成 \(d\) 组,一组中有 \(O(N/d)\) 个元素。
- 注意到每一组内,相邻像个元素的差是 \((A(i+d)+B) - (Ai+B) = Ad = h\) 。那么,假如当前元素是 \(x\),接下来的元素就是 \(x+h, x+2h, x+3h, \cdots\) 如果 \(0 \leqslant x < h\),那要超过 \(M\) 至少要经过 \(O(\frac{M}{h})\) 个元素。这个过程中经过的元素都不超过 \(M\),所以它们 \(\bmod M\) 之后不变,可以被直接划进一个等差数列。
- 于是,可以发现一组内可以每 \(O(M/h)\) 个元素划成一个等差数列,因此,一组可以被分成 \(O(\frac{N/d}{M/h}) = O(\frac{hN}{dM})\) 个等差数列。
- 共有 \(d\) 组,所以公共的等差数列数为 \(O(\frac{hN}{M})\) 。
为了让这个总等差数列数很小,我们需要找到一个足够小的 \(h\) 。
考虑设置一个阈值 \(D=\sqrt{10^9}\),然后观察 \(A \bmod M, 2A \bmod M, 3A \bmod M, \cdots, DA \bmod M\) 这 \(D\) 个数。根据抽屉原理,这 \(D\) 个数中最接近的两个数不超过 \(O(\frac{M}{D})\)。假设它们分别是 \(iA \bmod M\) 和 \(jA \bmod M\),那么 \((j-i)A \bmod M\) 就是一个绝对值不超过 \(\frac{M}{D}\) 的数了,可以令 \(d = j-i\),\(h = (j-i)A \bmod M\)。
此时,总等差数列数为 \(O(\frac{hN}{M}) = O(\frac{N}{D}) = O(D)\)
于是,使用上述方法划分出不超过 \(O(D)\) 个等差数列,再对每个等差数列以 \(O(\log 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 ll = long long;
using mint = modint;
// sum x^{a*i+b}
mint f(ll a, ll b, mint x, int n) {
mint res = 0, sum = 1, pw = x.pow(a);
while (n) {
if (n&1) res = res*pw + sum;
sum += sum*pw; pw *= pw;
n >>= 1;
}
return res*x.pow(b);
}
mint full(ll a, ll b, mint x, ll m) {
ll g = gcd(a, m);
return f(g, b%g, x, m/g)*g;
}
mint solve() {
int n, m, x, mod; ll a, b;
cin >> n >> m >> a >> b >> x >> mod;
mint::set_mod(mod);
mint ans;
if (a == 0) return mint(x).pow(b)*n;
if (n >= m) {
ans += full(a, b, x, m)*(n/m);
n %= m;
}
if (n <= 1) {
rep(i, n) ans += mint(x).pow((a*i+b)%m);
return ans;
}
const int D = sqrt(1e9);
int dy = m, dx = 1; bool rev = false;
{
vector<pair<int, int>> ps;
rep(i, min(D, n)) {
ps.emplace_back((a*i+b)%m, i);
}
sort(ps.begin(), ps.end());
rep(i, ps.size()-1) {
int ey = ps[i+1].first - ps[i].first;
int ex = ps[i+1].second - ps[i].second;
if (ey < dy) {
dy = ey;
dx = abs(ex);
rev = (ex < 0);
}
}
}
if (rev) {
b = (a*(n-1)+b)%m;
a = (m-a)%m;
}
rep(si, dx) {
for (int i = si; i < n;) {
ll c = (a*i+b)%m;
int w = dy ? (m-1-c)/dy+1 : n;
w = min(w, (n-1-i)/dx+1);
ans += f(dy, c, x, w);
i += dx*w;
}
}
return ans;
}
int main() {
int t;
cin >> t;
while (t--) cout << solve().val() << '\n';
return 0;
}
浙公网安备 33010602011771号