T1
A Bit Common
首先只需要考虑所有放了奇数的位置。发现所有奇数去掉最低位置后的 \(\texttt{AND}\) 和为 \(0\),也就是最低位外每一位上至少有 \(1\) 个 \(0\)。放偶数的位置怎么填都无所谓。枚举有几个奇数,答案即为 \(\sum\limits_{k = 1}^n \binom{n}{k}(2^k - 1)^{m - 1}2^{(n - i)(m - 1)}\)。
代码
#include <iostream>
#define int long long
using namespace std;
int n, m, P;
int C[5005][5005];
int fac[5005], pw[5005];
inline int qpow(int x, int y) {
int ret = 1;
while (y) {
if (y & 1)
ret = ret * x % P;
y >>= 1;
x = x * x % P;
}
return ret;
}
signed main() {
fac[0] = pw[0] = 1;
cin >> n >> m >> P;
for (int i = 1; i <= 5000; i++) fac[i] = fac[i - 1] * i % P, pw[i] = pw[i - 1] * 2 % P;
C[0][0] = 1 % P;
for (int i = 1; i <= 5000; i++) {
C[i][0] = 1 % P;
for (int j = 1; j <= i; j++) C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % P;
}
int ans = 0;
for (int i = 1; i <= n; i++) ans += C[n][i] * qpow(2, (n - i) * (m - 1)) % P * qpow(pw[i] - 1, (m - 1)) % P;
cout << ans % P << "\n";
return 0;
}
T2
A Bit More Common
考虑容斥,用第一题的答案减去那些只有一个合法子序列的方案数。还是只考虑奇数,也就是去掉最低位后要求与和为 \(0\) 的方案只有 \(1\) 种。这等价于去掉任何一个奇数都不能再有合法的方案。对于一个奇数,如果它上面的所有 \(0\) 位都至少有 \(2\) 个 \(0\),那这个奇数其实就可以删掉了。也就是说对于每个奇数,它至少有 \(1\) 个为 \(0\) 的位是别的奇数在这一位都非 \(0\) 的。我们将这样的位称为特殊位,则就是说每一个数都至少占据 \(1\) 个特殊位。我们考虑先算出在 \(i\) 个数里分配 \(j\) 个特殊位的方案数,它应当就是 \(S(j, i) \times i \space !\),其中 \(S\) 表示第二类斯特林数。然后就可以直接算了。选了 \(i(i > 1)\) 个奇数的不合法方案数为 \(\sum\limits_{j = i}^{m - 1} \binom{n}{i}\binom{m - 1}{j}S(j, i)i!(2^i - i - 1)^{m - 1 - j}2^{(n - i)(m - 1)}\),其中 \(j\) 即为特殊位个数。
代码
#include <iostream>
#define int long long
using namespace std;
int n, m, P;
int C[5005][5005];
int fac[5005], pw[5005];
inline int qpow(int x, int y) {
int ret = 1;
while (y) {
if (y & 1)
ret = ret * x % P;
y >>= 1;
x = x * x % P;
}
return ret;
}
int S[5005][5005];
int pw2[5005];
signed main() {
fac[0] = pw[0] = pw2[0] = 1;
cin >> n >> m >> P;
for (int i = 1; i <= 5000; i++) fac[i] = fac[i - 1] * i % P, pw[i] = pw[i - 1] * 2 % P;
S[0][0] = C[0][0] = 1 % P;
for (int i = 1; i <= 5000; i++) {
pw2[i] = pw2[i - 1] * pw[m - 1] % P;
C[i][0] = 1 % P;
for (int j = 1; j <= i; j++) {
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % P;
S[i][j] = (S[i - 1][j - 1] + j * S[i - 1][j]) % P;
}
}
int ans = 0;
for (int i = 2; i <= n; i++) {
ans += C[n][i] * qpow(2, (n - i) * (m - 1)) % P * qpow(pw[i] - 1, (m - 1)) % P;
for (int j = m - 1, cur = 1; j >= i; j--, cur = cur * (pw[i] + P - i - 1) % P) ans += P - C[n][i] * C[m - 1][j] % P * S[j][i] % P * fac[i] % P * cur % P * pw2[n - i] % P;
ans %= P;
}
cout << ans << "\n";
return 0;
}
T3
Sum of Suffix Sums
位置 \(i\) 的数的贡献即为 \(a_i \times i\),随便做。
代码
太简单了,不放了。
T4
XOR of Suffix Sums
注意到模数是 \(2^{21}\),于是只需要考虑低 \(21\) 位。不难想到对每一位计算贡献。设前缀和数组为 \(p_i\),则第 \(i\) 位对答案有贡献当且仅当存在奇数个 \(j\) 使得 \((p_n - p_j) \bmod 2^{i + 1} \ge 2^i\),也就是 \((p_n \bmod 2^{i + 1}) + ((-p_j) \bmod 2^{i + 1}) \bmod 2^{i + 1} \ge 2^i\)。这样就可以想到维护所有 \((-p_j) \bmod 2^{i + 1}\),然后每次用 \(p_n \bmod 2^{i + 1}\) 去查询。查询会分为至多两个区间。这样就能统计出合法 \(j\) 的个数,然后就做完了。
代码
#include <iostream>
#define int long long
#define lowbit(x) ((x) & (-(x)))
using namespace std;
int q;
int pre[500005], sz;
struct BIT {
int bit[2100005];
void add(int x, int y) { for (++x; x <= 2100000; x += lowbit(x)) bit[x] += y; }
int query(int x) {
int ret = 0;
for (++x; x; x -= lowbit(x)) ret += bit[x];
return ret;
}
int query(int l, int r) { return query(r) - (l >= 0 ? query(l - 1) : 0); }
} bit[22];
void pop_back() { for (int i = 0; i < 21; i++) bit[i].add(((1 << (i + 1)) - pre[sz] % (1 << (i + 1))) % (1 << (i + 1)), -1); --sz; }
int ans;
void push_back(int v) {
ans = 0;
++sz;
pre[sz] = pre[sz - 1] + v;
for (int i = 0; i < 21; i++) {
int p = (1 << (i + 1)), x = (1 << i), a = pre[sz] % p;
ans |= (((bit[i].query(x - a, p - a - 1) + bit[i].query(p + x - a, p)) & 1) << i);
bit[i].add((p - pre[sz] % p) % p, 1);
}
}
signed main() {
for (int i = 0; i < 21; i++) bit[i].add(0, 1);
cin >> q;
while (q--) {
int x, y;
cin >> x >> y;
while (x--) pop_back();
push_back(y);
cout << ans << "\n";
}
return 0;
}
T8
World Finals
本队参加第一场时,把所有能放第二场的全放第二场。本队参加第二场时,把所有能放第一场的全放第一场。两次分别算个答案取 \(\min\) 即可。
代码
#include <iostream>
#include <algorithm>
#include <map>
using namespace std;
int n, m;
string me = "lzr010506";
struct node {
string name;
int sp, pen;
} a[100005], b[100005], c[100005];
map<string, int> mp1, mp2;
int d;
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i].name >> a[i].sp >> a[i].pen, mp1[a[i].name] = 1;
cin >> m;
for (int i = 1; i <= m; i++) cin >> b[i].name >> b[i].sp >> b[i].pen, mp2[b[i].name] = 1;
for (int i = 1; i <= n; i++) {
if (!mp2.count(a[i].name) || a[i].name == me)
c[++d] = a[i];
}
sort(c + 1, c + d + 1, [](node a, node b) { return a.sp == b.sp ? (a.pen < b.pen) : (a.sp > b.sp); });
int ans = 2147483647;
for (int i = 1; i <= d; i++) {
if (c[i].name == me)
ans = min(ans, i);
}
d = 0;
for (int i = 1; i <= m; i++) {
if (!mp1.count(b[i].name) || b[i].name == me)
c[++d] = b[i];
}
sort(c + 1, c + d + 1, [](node a, node b) { return a.sp == b.sp ? (a.pen < b.pen) : (a.sp > b.sp); });
for (int i = 1; i <= d; i++) {
if (c[i].name == me)
ans = min(ans, i);
}
cout << ans << "\n";
return 0;
}
T9
Mirror Maze
容易想到对每个格子拆 \(4\) 个点分别表示这个格子出去的四个方向。每个格子每个方向的点向其对应方向照过去的点经过反射后的方向连边,如果发生了反射则在这条边上标记这个反射镜的编号。由于光路可逆,而光不会分叉,因此光也不会汇合,所以这样连出来的就是一堆环和链。对于链,从最终的点往回 dfs,中途记录所有用到的反射镜并算出链上每个点走到终点会被几面镜子反射。对于环,找到其中所有用到的反射镜扔 set 去重一下即可。同一环中所有点答案同为其 set 大小。
代码
#include <iostream>
#include <queue>
#include <set>
using namespace std;
int n, m;
string str[1005];
// 0123
// RDLU
int id[1005][1005][4], ncnt;
int head[4000005], nxt[16000005], to[16000005], ew[16000005], ecnt;
queue<int> q;
int val[4000005];
int deg[4000005];
int vis[4000005];
bool vis2[4000005];
void add(int u, int v, int ww = -1) { to[++ecnt] = u, nxt[ecnt] = head[v], head[v] = ecnt, ew[ecnt] = ww; }
struct DSU {
int f[4000005], val[4000005];
void ini(int x) { for (; x; --x) f[x] = x; }
int getf(int x) { return (f[x] == x ? x : (f[x] = getf(f[x]))); }
void link(int x, int y) {
x = getf(x), y = getf(y);
if (x == y)
return;
f[x] = y;
val[y] += val[x];
}
} dsu;
int cur;
set<int> st[4000005];
void dfs(int x) {
vis2[x] = 1;
val[x] = cur;
for (int i = head[x]; i; i = nxt[i]) {
cur += (ew[i] != -1 && (vis[ew[i]]++ == 0));
dfs(to[i]);
cur -= (ew[i] != -1 && (--vis[ew[i]] == 0));
}
}
int main() {
// freopen("D.in", "r", stdin);
// freopen("D.out", "w", stdout);
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> str[i], str[i] = ' ' + str[i];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
id[i][j][0] = ncnt++;
id[i][j][1] = ncnt++;
id[i][j][2] = ncnt++;
id[i][j][3] = ncnt++;
// cout << i << " " << j << " " << id[i][j][0] << "\n";
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (j != m) {
if (str[i][j + 1] == '\\') {
add(id[i][j][0], id[i][j + 1][1], id[i][j + 1][0] / 4);
}
else if (str[i][j + 1] == '/') {
// cout << i << " " << j << " r\n";
add(id[i][j][0], id[i][j + 1][3], id[i][j + 1][0] / 4);
}
else if (str[i][j + 1] == '|')
add(id[i][j][0], id[i][j + 1][2], id[i][j + 1][0] / 4);
else
add(id[i][j][0], id[i][j + 1][0]);
}
if (i != n) {
if (str[i + 1][j] == '\\')
add(id[i][j][1], id[i + 1][j][0], id[i + 1][j][0] / 4);
else if (str[i + 1][j] == '/')
add(id[i][j][1], id[i + 1][j][2], id[i + 1][j][0] / 4);
else if (str[i + 1][j] == '|')
add(id[i][j][1], id[i + 1][j][1]);
else
add(id[i][j][1], id[i + 1][j][3], id[i + 1][j][0] / 4);
}
if (j != 1) {
if (str[i][j - 1] == '\\')
add(id[i][j][2], id[i][j - 1][3], id[i][j - 1][0] / 4);
else if (str[i][j - 1] == '/')
add(id[i][j][2], id[i][j - 1][1], id[i][j - 1][0] / 4);
else if (str[i][j - 1] == '|')
add(id[i][j][2], id[i][j - 1][0], id[i][j - 1][0] / 4);
else
add(id[i][j][2], id[i][j - 1][2]);
}
if (i != 1) {
if (str[i - 1][j] == '\\')
add(id[i][j][3], id[i - 1][j][2], id[i - 1][j][0] / 4);
else if (str[i - 1][j] == '/')
add(id[i][j][3], id[i - 1][j][0], id[i - 1][j][0] / 4);
else if (str[i - 1][j] == '|')
add(id[i][j][3], id[i - 1][j][3]);
else
add(id[i][j][3], id[i - 1][j][1], id[i - 1][j][0] / 4);
}
}
}
dsu.ini(n * m * 4);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (i == 1)
dfs(id[i][j][3]);
if (i == n)
dfs(id[i][j][1]);
if (j == 1)
dfs(id[i][j][2]);
if (j == m)
dfs(id[i][j][0]);
}
}
for (int i = 0; i < ncnt; i++) {
if (vis2[i])
continue;
// cout << i / 4 << " " << i << "\n";
for (int j = head[i]; j; j = nxt[j]) {
int v = to[j];
if (vis2[v])
continue;
dsu.link(i, v);
}
}
for (int i = 0; i < ncnt; i++) {
if (vis2[i])
continue;
for (int j = head[i]; j; j = nxt[j]) {
int v = to[j];
// cout << i << " " << v << " " << ew[j] << "\n";
// cout << i << " " << dsu.getf(i) << "\n";
if (vis2[v])
continue;
if (ew[j] != -1)
st[dsu.getf(i)].insert(ew[j]);
}
}
for (int i = 0; i < ncnt; i++) {
if (!vis2[i])
val[i] = st[dsu.getf(i)].size();
}
int qs;
cin >> qs;
while (qs--) {
int x, y;
string s;
cin >> x >> y >> s;
int c;
if (s == "right")
c = id[x][y][0];
else if (s == "below")
c = id[x][y][1];
else if (s == "left")
c = id[x][y][2];
else
c = id[x][y][3];
cout << val[c] << "\n";
}
return 0;
}
T10
2D Travel
容易发现两维独立。对于一维的情况,我们考虑在一次撞墙之后,本质不同的情况就是当前在操作序列的什么位置。因此考虑求出若当前执行完了操作序列的第 \(i\) 项,当前在左 / 右墙,下一次撞墙是在执行完哪一个操作之后。然后就可以倍增了。对于一次询问,先求出它第一次撞墙是在什么时候,然后倍增,最后对于剩下的没撞墙的一段特殊处理即可。
代码
会写的。