C. Rotate and Sum Query
如果没有第一种查询,只要直接做前缀和就行。
即使有第一种查询,也不必真的去移动元素,只要把下标整体偏移一下,记住“原序列中每个元素现在排在第几位”,就能知道想要的区间和在原序列的哪一段!
代码实现
#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, q;
cin >> n >> q;
vector<int> a(n);
rep(i, n) cin >> a[i];
vector<ll> s(n+1);
rep(i, n) s[i+1] = s[i]+a[i];
int si = 0;
rep(qi, q) {
int type;
cin >> type;
if (type == 1) {
int c;
cin >> c;
si = (si+c)%n;
}
else {
int l, r;
cin >> l >> r;
--l; --r;
l = (l+si)%n;
r = (r+si)%n;
ll ans;
if (l <= r) ans = s[r+1]-s[l];
else ans = s[n] - (s[l]-s[r+1]);
cout << ans << '\n';
}
}
return 0;
}
D. Ulam-Warburton Automaton
模拟
黑格子保持黑色,只有与“上一轮操作中新变黑的格子”相邻的白色格子,才可能在这一轮变成黑色,所以每次只要检查这些格子就行!而上一轮操作中新变黑的格子总数不超过 \(HW\) 个,因此整体时间复杂度为 \(O(HW)\)!
代码实现1
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using P = pair<int, int>;
const int di[] = {-1, 0, 1, 0};
const int dj[] = {0, 1, 0, -1};
int main() {
int h, w;
cin >> h >> w;
vector<string> s(h);
rep(i, h) cin >> s[i];
const int INF = 1001001001;
vector step(h, vector<int>(w, INF));
queue<P> q;
rep(i, h)rep(j, w) if (s[i][j] == '#') {
step[i][j] = 0;
q.emplace(i, j);
}
int ans = 0;
while (q.size()) {
ans++;
auto [i, j] = q.front(); q.pop();
int si = step[i][j];
rep(v, 4) {
int ni = i+di[v], nj = j+dj[v];
if (ni < 0 or ni >= h or nj < 0 or nj >= w) continue;
if (step[ni][nj] != INF) continue;
int cnt = 0;
rep(v2, 4) {
int mi = ni+di[v2], mj = nj+dj[v2];
if (mi < 0 or mi >= h or mj < 0 or mj >= w) continue;
if (step[mi][mj] <= si) cnt++;
}
if (cnt == 1) {
step[ni][nj] = si+1;
q.emplace(ni, nj);
}
}
}
cout << ans << '\n';
return 0;
}
代码实现2
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using P = pair<int, int>;
const int di[] = {-1, 0, 1, 0};
const int dj[] = {0, 1, 0, -1};
int main() {
int h, w;
cin >> h >> w;
vector<string> s(h);
rep(i, h) cin >> s[i];
vector<P> updatedCells;
rep(i, h)rep(j, w) if (s[i][j] == '#') {
updatedCells.emplace_back(i, j);
}
int ans = 0;
while (updatedCells.size()) {
ans += updatedCells.size();
vector<P> nextCells;
for (auto [i, j] : updatedCells) {
rep(v, 4) {
int ni = i+di[v], nj = j+dj[v];
if (ni < 0 or ni >= h or nj < 0 or nj >= w) continue;
if (s[ni][nj] == '#') continue;
int cnt = 0;
rep(v2, 4) {
int mi = ni+di[v2], mj = nj+dj[v2];
if (mi < 0 or mi >= h or mj < 0 or mj >= w) continue;
if (s[mi][mj] == '#') cnt++;
}
if (cnt == 1) {
nextCells.emplace_back(ni, nj);
}
}
}
for (auto [i, j] : nextCells) {
s[i][j] = '#';
}
swap(updatedCells, nextCells);
}
cout << ans << '\n';
return 0;
}
E. Count Sequences 2
答案 \(= \dbinom{C_1}{C_1} \times \dbinom{C_1+C_2}{C_2} \times \dbinom{C_1+C_2+C_3}{C_3} \times \cdots\)
用杨辉三角预处理组合数即可
代码实现
#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 = modint;
const int M = 5000;
mint comb[M+1][M+1];
void solve() {
int n;
cin >> n;
int s = 0;
mint ans = 1;
rep(i, n) {
int c;
cin >> c;
s += c;
ans *= comb[s][c];
}
cout << ans.val() << '\n';
}
int main() {
int t, mod;
cin >> t >> mod;
mint::set_mod(mod);
comb[0][0] = 1;
rep(i, M)rep(j, i+1) {
comb[i+1][j] += comb[i][j];
comb[i+1][j+1] += comb[i][j];
}
while (t--) solve();
return 0;
}
F. Inserting Process
首先,这是一个统计逐字符删除操作序列数量的问题。删除方式不同却得到相同字符串,仅在连续相同字符区间删除位置不同才会发生。因此,把“在连续相同字符区间内只能选最前面的字符”作为条件,然后以当前剩余字符作为状态做状压dp即可!
代码实现
#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;
int main() {
int n;
string t;
cin >> n >> t;
int n2 = 1<<n;
vector<mint> dp(n2);
dp[n2-1] = 1;
for (int s = n2-1; s; --s) {
char pre = '-';
rep(i, n) if (s>>i&1) {
if (pre != t[i]) dp[s^1<<i] += dp[s];
pre = t[i];
}
}
mint ans = dp[0];
cout << ans.val() << '\n';
return 0;
}
G. Sum of Min of XOR
对于单个 \(x\),通过 \(01 \mathrm{Trie}\) 可以用贪心法求最小异或值:从最高位向下走,若存在与 \(x\) 当前位相同的子节点就走那边(使当前位的 \(XOR = 0\)),否则只能走相反位(当前位的 \(XOR = 1\),贡献 \(2^i\)),继续处理下一位。最终得到的路径对应的数就是 \(\min_i (x\oplus A_i\))。
把“对每个 \(x\) 在 \(01 \mathrm{Trie}\) 上贪心求最小 \(XOR\)”的操作并行化:按位从高到低把区间 \([0,M)\) 划分成 \(2^i\) 对齐的块,用状态 (trie-node, block-length) 表示“在当前高位信息下剩余要处理的连续 \(x\)”,若某一位对于该状态必然产生 \(1\) 则直接把该位贡献累加并把这些 \(x\) 送到相反子树继续处理,否则把它们送到存在的同位子树。
代码实现
#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>;
const int K = 30;
struct BinaryTrie {
struct Node { int l, r; };
vector<Node> nodes;
BinaryTrie(): nodes(1, Node(-1, -1)) {}
int getNode(int& v, bool create=false) {
if (!create or v != -1) return v;
int res = (v = nodes.size());
nodes.emplace_back(-1, -1);
return res;
}
int go(int v, int d, bool create=false) {
if (d == 0) return getNode(nodes[v].l, create);
else return getNode(nodes[v].r, create);
}
void add(int x) {
int v = 0;
for (int i = K-1; i >= 0; --i) {
v = go(v, x>>i&1, true);
}
}
};
int main() {
int n, m;
cin >> n >> m;
BinaryTrie t;
rep(i, n) {
int a;
cin >> a;
t.add(a);
}
ll ans = 0;
map<P, ll> d;
d[P(0, m)] = 1;
for (int i = K-1; i >= 0; --i) {
int b = 1<<i;
map<P, ll> old;
swap(d, old);
for (auto [p, x] : old) {
auto [v, r] = p;
int r0 = min(r, b);
int r1 = r-b;
int v0 = t.go(v, 0);
int v1 = t.go(v, 1);
{
if (v0 == -1) {
ans += (ll)r0*b*x;
d[P(v1, r0)] += x;
}
else {
d[P(v0, r0)] += x;
}
}
if (r1 > 0) {
if (v1 == -1) {
ans += (ll)r1*b*x;
d[P(v0, r1)] += x;
}
else {
d[P(v1, r1)] += x;
}
}
}
}
cout << ans << '\n';
return 0;
}
浙公网安备 33010602011771号