2024牛客暑期多校训练营1
A - A Bit Common
对于\(A\),最优解肯定是选择所有最低位为\(1\)的数。所以我枚举最低位为一的数的个数\(x\)。
对于这个\(x\)个数,高位每一位的选择方法有\(2^x\)个,其中只有全\(1\)的情况与为\(1\),其他的\(x^x-1\)种都是\(0\),共有 \(m-1\)位,所以种类有\((2^x - 1)^{m-1}\)种。
对于剩下的\(n-x\)个数,高位任意取,所以\((2^x)^{m-1}\)。
然后排列顺序有\(C_n^x\)种。
所以最终的答案就是
#include<bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
#define int i64
const int inf = INT_MAX / 2;
int mod;
int power(int x, int y) {
int ans = 1;
while (y) {
if (y & 1) ans = ans * x % mod;
x = x * x % mod, y /= 2;
}
return ans;
}
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n, m;
cin >> n >> m >> mod;
int res = 0;
map<int, int> cnt;
for (int x = 1, p, q, y; x <= n; x++) {
p = power(power(2, m - 1), n - x);
q = power((power(2, x) - 1 + mod) % mod, m - 1);
// 分子
y = n - x + 1;
for (int i = 2; i * i <= y; i++) {
if (y % i != 0) continue;
while (y % i == 0) cnt[i]++, y /= i;
}
if (y > 1) cnt[y]++;
// 分母
y = x;
for (int i = 2; i * i <= y; i++) {
if (y % i != 0) continue;
while (y % i == 0) cnt[i]--, y /= i;
}
if (y > 1) cnt[y]--;
y = 1;
for (const auto &[pi, qi]: cnt)
y = y * power(pi, qi) % mod;
res = (res + p * q % mod * y % mod) % mod;
}
cout << res;
return 0;
}
B - A Bit More Common
考虑这题与\(A\)题的不同。
题目要求的是至少两个子序列的与为\(1\)。正难则反,考虑求出只有一个子序列按位与为\(1\)的情况。然后用第一题答案减去只有一个的子序列的情况。
可以想到的是,只有一个序列的情况应当是选择所有最低位为 \(1\)的数字。那
依旧可以枚举最低位为\(1\)的数的个数为\(k\)。那么对于某一位来说,可以看作是一个\(k\)位的二进制数。
对于除最低外任意一位,如果有且仅有一个\(0\)的情况,那么这个\(0\)对应的数字应当是必须选择的。我们把这些位置称作“特殊位”。
如果对于\(k\)个数,每个数都至少有一个特殊位,哪么最终的情况一定只有一个就是这\(k\)个数全部都选择。
我们可以考虑枚举特殊位的个数\(t\)。
首先对于特殊位来说,我们可以采用dp求解,记\(f[i][j]\)表示\(i\)个数字有\(j\)个特殊位的放置方法,则存在转移
这个转移的考虑方法,可以参考第二类斯特林数。
对于非特殊位来说,总共的方案数有\(2^k\),其中全 1 的有\(1\)中,只有一个\(0\)的有\(k\)种,所以剩下可用的有\((2^k - k - 1)\)种,还剩下\(m - 1 - t\)位,所以方案为\((2^k - k - 1) ^{m-1}\)
还要考虑那些位是特殊位,共有\(C_{m - 1} ^{t}\)种
因此如果有\(t\)个特殊位,则共有\(C_{m-1} ^ t \times f[k][t] \times (2 ^ k - k - 1 ) ^{m-1}\) 种。
考虑特殊的数量有\(t\in [k,m]\),所以所有只有一种的情况就是
用A题答案减去上述答案即可。
但是本题有些卡常,所以请尽量减少使用快速幂
#include<bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
#define int i64
using vi = vector<int>;
const int inf = INT_MAX / 2;
int mod;
int power(int x, int y) {
int ans = 1;
while (y) {
if (y & 1) ans = ans * x % mod;
x = x * x % mod, y /= 2;
}
return ans;
}
const int N = 5005;
int C[N][N], S[N][N], pow2[N * N], powM[N];
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n, m;
cin >> n >> m >> mod;
pow2[0] = 1;
for (int i = 1, t = n * m; i <= t; i++)
pow2[i] = pow2[i - 1] * 2 % mod;
C[0][0] = 1;
for (int i = 1, t = max(n, m); i <= t; i++) {
C[i][0] = 1;
for (int j = 1; j <= i; j++)
C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
}
S[0][0] = 1;
for (int i = 1; i <= n; i++)
for (int j = i; j <= m; j++)
S[i][j] = i * (S[i][j - 1] + S[i - 1][j - 1]) % mod;
int res = 0;
for (int k = 2, p, q; k <= n; k++) {
p = C[n][k] * pow2[(n - k) * (m - 1)] % mod;
q = power((pow2[k] - 1 + mod) % mod, m - 1);
powM[0] = 1;
for (int i = 1; i <= m; i++)
powM[i] = powM[i - 1] * (pow2[k] - k - 1 + mod) % mod;
for (int t = k; t <= m; t++)
q = (q - C[m - 1][t] * S[k][t] % mod * powM[m - 1 - t] % mod + mod) % mod;
res = (res + p * q % mod) % mod;
}
cout << res % mod;
return 0;
}
C - Sum of Suffix Sums
答案就是
#include<bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
#define int i64
using vi = vector<int>;
using pii = pair<int, int>;
const int inf = INT_MAX / 2;
const int mod = 1e9 + 7;
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int q;
cin >> q;
vi a;
int res = 0;
for (int t, v; q; q--) {
cin >> t >> v;
while (t--) {
res = (res - a.back() + mod) % mod;
a.pop_back();
}
a.push_back((v * (a.size() + 1)) % mod);
res = (res + a.back()) % mod;
cout << res << "\n";
}
return 0;
}
D - XOR of Suffix Sums
考虑与上一题的不同,看起来可以用使用区间修改和区间求异或和。但实际上这样做很难维护且复杂度很高。
观察这个模数\(2097152 = 2 ^{21}\),因此这个相当于只用考虑\(21\)位即可。
考虑如果快速的维护后缀和。对于后缀和我门可以用前缀和来维护,也就是\(suf_i = pre _n - pre_{i-1}\)。
我们考虑某一位,我们如果知道了\(pre_n - pre_i-1\) 中有多少个数字的这一位为\(1\)。就可以知道这一位在异或和中的贡献。
假设我们要求的是第\(d\)位,现在我们已经知道了\(pre_n\)中第\(d\)为是\(0\),只要求出有多少个数使得这一位变为\(1\)即可,如果是\(1\)就是多少个数使第\(d\)位不变即可。这些数如果我们只看前\(d+1\)位,就一定是一个连续的区间。因此可以使用值域树状数组求解。
#include <bits/stdc++.h>
using namespace std;
using vi = vector<int>;
struct BinaryIndexedTree {
int n;
vi b;
BinaryIndexedTree() {}
BinaryIndexedTree(int n) : n(n), b(n + 1) {};
void resize(int newSize) {
n = newSize;
b = vi(n + 1);
return;
}
int lowbit(int x) {
return (x & -x);
}
void modify(int i, int y) {
for (; i <= n; i += lowbit(i))
b[i] += y;
return;
}
int calc(int i) {
int ans = 0;
for (; i; i -= lowbit(i))
ans += b[i];
return ans;
}
int calc(int l, int r) {
return calc(r) - calc(l - 1);
}
};
const int M = 21, N = 5e6 + 10;
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int q;
cin >> q;
vector<BinaryIndexedTree> bit(M, BinaryIndexedTree(N));
vi pre(1, 0);
auto modify = [&bit](int x, int v) -> void {
for (int i = 0, y, t = 2; i < M; i++, t <<= 1)
y = x & (t - 1), bit[i].modify(y + 1, v);
return;
};
auto query = [&bit](int x) -> int {
int ans = 0;
for (int i = 0, y, t = 2; i < M; i++, t <<= 1) {
y = x & (t - 1);
if ((y & (1 << i)) == 0) { // 0xxx的情况
int l = y + 1, r = (1 << i) + y; // (0xxx, 1xxx]
ans += (bit[i].calc(l + 1, r + 1) & 1) << i;
} else { // 1xxx
int l1 = 0, r1 = y - (1 << i); // [0, 0xxx]
int l2 = y + 1, r2 = y + (1 << i); // (1xxx, 10xxx] -> (1xxx, 111] + [10000, 10xxx]
ans += ((bit[i].calc(l1 + 1, r1 + 1) + bit[i].calc(l2 + 1, r2 + 1)) & 1) << i;
}
}
return ans;
};
for (int t, v; q; q--) {
cin >> t >> v;
while (t--) {
modify(pre[pre.size() - 2], -1);
pre.pop_back();
}
pre.push_back(pre.back() + v);
modify(pre[pre.size() - 2], 1);
cout << query(pre.back()) << "\n";
}
return 0;
}
H - World Finals
考虑贪心,把能换的人都换走之后,在计算排名就好了
#include<bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
#define int i64
const int inf = INT_MAX / 2;
struct node {
string name;
int p, t;
node() {}
node(string name, int p, int t) : name(name), p(p), t(t) {};
bool operator<(node b) const {
if (p != b.p) return p > b.p;
return t < b.t;
}
};
const string lzr = "lzr010506";
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n;
cin >> n;
vector<node> a(n);
set<string> nameA;
for (auto &[name, p, t]: a)
cin >> name >> p >> t, nameA.insert(name);
int m;
cin >> m;
vector<node> b(m);
set<string> nameB;
for (auto &[name, p, t]: b)
cin >> name >> p >> t, nameB.insert(name);
int ans = 0;
sort(a.begin(), a.end());
for (auto &[name, p, t]: a) {
ans++;
if (name == lzr) break;
if (nameB.count(name)) ans--;
}
int res = 0;
sort(b.begin(), b.end());
for (auto &[name, p, t]: b) {
res++;
if (name == lzr) break;
if (nameA.count(name)) res--;
}
cout << min(ans, res);
return 0;
}
I - Mirror Maze
考虑到光路可逆,因此所有的光路要么是一条链,要么是一个环。因此我们可以建图,对于每个镜子,我们建四个点,分别表示光线从四个方向射出去。然后对于链和环的情况,分别用搜索统计一下答案。最后\(O(1)\)回答就好了。
#include<bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
using vi = vector<int>;
const vi dx = {-1, 1, 0, 0};
const vi dy = {0, 0, -1, 1};
// t 0...3 上下左右
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n, m;
cin >> n >> m;
vector<string> g(n + 1);
for (int i = 0; i < n; i++)
cin >> g[i];
auto f = [&](int x, int y, int t) -> int {
return (x * m + y) * 4 + t;
};
int N = n * m * 4;
vector<vi> e(N);
vi deg(N);
for (int x = 0; x < n; x++)
for (int y = 0; y < m; y++)
for (int t = 0, fx, fy, ft; t < 4; t++) {
fx = x + dx[t], fy = y + dy[t];
if (fx < 0 or fy < 0 or fx >= n or fy >= m) continue;
if (g[fx][fy] == '-') {
if (t == 0) ft = 1;
if (t == 1) ft = 0;
if (t == 2) ft = 2;
if (t == 3) ft = 3;
} else if (g[fx][fy] == '|') {
if (t == 0) ft = 0;
if (t == 1) ft = 1;
if (t == 2) ft = 3;
if (t == 3) ft = 2;
} else if (g[fx][fy] == '\\') {
if (t == 0) ft = 2;
if (t == 1) ft = 3;
if (t == 2) ft = 0;
if (t == 3) ft = 1;
} else {
if (t == 0) ft = 3;
if (t == 1) ft = 2;
if (t == 2) ft = 1;
if (t == 3) ft = 0;
}
e[f(x, y, t)].push_back(f(fx, fy, ft));
deg[f(fx, fy, ft)] = 1;
}
vi res(N), vis(N);
set<int> cnt;
auto dfs = [&](auto &&dfs, int x) -> void {
vis[x] = 1;
if (e[x].empty()) return;
int y = e[x].front();
dfs(dfs, y);
if (x % 4 != y % 4) cnt.insert(y / 4);
res[x] = cnt.size();
return;
};
for (int i = 0; i < N; i++) {
if (deg[i]) continue;
cnt.clear();
dfs(dfs, i);
}
for (int i = 0; i < N; i++) {
if (vis[i]) continue;
queue<int> q;
q.push(i);
cnt.clear();
while (not q.empty()) {
int x = q.front();
q.pop();
vis[x] = 1;
if (e[x].empty()) continue;
int y = e[x].front();
if (x % 4 != y % 4) cnt.insert(y / 4);
if (vis[y]) continue;
res[y] = -i, q.push(y);
}
res[i] = cnt.size();
}
int q;
cin >> q;
string dir;
for (int x, y, t, r; q; q--) {
cin >> x >> y >> dir, x--, y--;
if (dir == "above") t = 0;
if (dir == "below") t = 1;
if (dir == "left") t = 2;
if (dir == "right") t = 3;
r = f(x, y, t);
if (res[r] < 0) cout << res[-res[r]] << "\n";
else cout << res[r] << "\n";
}
return 0;
}