20250218
T1
七管荧光灯
需要先用打表等方式注意到当七个数为 \(\{ a, a, a, b, c, c, c \}\) 这样的形式且 \(a \oplus b \oplus c = 0\) 时才会先手必败。因此需要求出 \(a \in [l_1, r_1], b \in [l_2, r_2], c \in [l_3, r_3]\) 且异或和为 \(0\) 的方案数。考虑容斥之后数位 dp,从高位往低位递推,\(f_{i, 0 / 1, 0 / 1, 0 / 1}\) 分别表示考虑完高 \(i\) 位,三个数当前是否有限制的方案数。转移一位考虑当前哪两个数填 \(1\) 或者都填 \(0\)。
代码
#include <iostream>
#include <string.h>
#define int long long
using namespace std;
const int P = 998244353;
void Madd(int &x, int y) { (x += y) >= P ? (x -= P) : 0; }
int l[10], r[10], ans = 1;
int f[105][2][2][2];
int calc(int a, int b, int c) {
if (a < 0 || b < 0 || c < 0)
return 0;
memset(f, 0, sizeof f);
f[62][0][0][0] = 1;
for (int i = 61; ~i; i--) {
for (int x : { 0, 1 }) {
for (int y : { 0, 1 }) {
for (int z : { 0, 1 }) {
if (!f[i + 1][x][y][z])
continue;
if (((a >> i) & 1) || x) {
if (((b >> i) & 1) || y)
Madd(f[i][x][y][z | ((c >> i) & 1)], f[i + 1][x][y][z]);
if (((c >> i) & 1) || z)
Madd(f[i][x][y | ((b >> i) & 1)][z], f[i + 1][x][y][z]);
}
if (((b >> i) & 1) || y) {
if (((c >> i) & 1) || z)
Madd(f[i][x | ((a >> i) & 1)][y][z], f[i + 1][x][y][z]);
}
Madd(f[i][x | ((a >> i) & 1)][y | ((b >> i) & 1)][z | ((c >> i) & 1)], f[i + 1][x][y][z]);
}
}
}
}
int ret = 0;
for (int x : { 0, 1 }) {
for (int y : { 0, 1 }) {
for (int z : { 0, 1 })
Madd(ret, f[0][x][y][z]);
}
}
return ret;
}
signed main() {
freopen("qgygd.in", "r", stdin);
freopen("qgygd.out", "w", stdout);
for (int i = 1; i <= 7; i++) cin >> l[i] >> r[i];
for (int i = 1; i <= 7; i++) ans = ans * ((r[i] - l[i] + 1) % P) % P;
for (int i = 1; i <= 3; i++) l[1] = max(l[1], l[i]), r[1] = min(r[1], r[i]);
for (int i = 5; i <= 7; i++) l[5] = max(l[5], l[i]), r[5] = min(r[5], r[i]);
if (l[1] > r[1] || l[4] > r[4] || l[5] > r[5])
cout << ans << "\n";
else
cout << (ans - (calc(r[1], r[5], r[4]) - calc(r[1], r[5], l[4] - 1) - calc(l[1] - 1, r[5], r[4]) - calc(r[1], l[5] - 1, r[4])
+ calc(l[1] - 1, l[5] - 1, r[4]) + calc(l[1] - 1, r[5], l[4] - 1) + calc(r[1], l[5] - 1, l[4] - 1)
- calc(l[1] - 1, l[5] - 1, l[4] - 1) + P * 10) % P + P) % P << "\n";
return 0;
}
T2
万宁俄罗斯方块(大招版)
落下时矩形将下表面封死所得的洞是容易线段树维护的。考虑侧边。定义一个半洞为一个横坐标上一段连续空白的纵坐标使得这段纵坐标左右中的一侧和上下都被封死。发现新加入一个矩形的时候,只需要考虑 \(l - 1\) 和 \(r + 1\) 两个横坐标上的半洞,只有这些才会新增洞。而加入之后最多只会在 \(l, r\) 两个横坐标上各增加一个半洞。使用 set 维护每个横坐标上的半洞,加入一个矩形要新增洞时暴力把组成新洞的半洞删掉即可。有一些细节,比如一个洞可能被下落矩形封死整个右上角。这个时候它既是半洞又是下表面的连续段,需要特殊处理。
代码
#pragma GCC optimize("Ofast")
#include <iostream>
#include <algorithm>
#include <string.h>
#include <cassert>
#include <time.h>
#include <array>
#include <set>
#include <map>
using namespace std;
#define getchar() p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++
char buf[1<<21], *p1, *p2, ch;
long long read() {
long long ret = 0, neg = 0; char c = getchar(); neg = (c == '-');
while (c < '0' || c > '9') c = getchar(), neg |= (c == '-');
while (c >= '0' && c <= '9') ret = ret * 10 + c - '0', c = getchar();
return ret * (neg ? -1 : 1);
}
const int N = 100000;
int n;
struct node {
int mx, pl, pr, cs, tg;
} T[400005];
node operator+(node a, node b) {
node c;
c.mx = max(a.mx, b.mx);
c.pl = (a.mx == c.mx ? a.pl : b.pl);
c.pr = (b.mx == c.mx ? b.pr : a.pr);
c.cs = (a.mx == c.mx) * a.cs + (b.mx == c.mx) * b.cs - (a.mx == b.mx) * (a.pr + 1 == b.pl);
c.tg = -1;
return c;
}
struct Segment_Tree {
inline void tag(int o, int l, int r, int v) { T[o] = (node) { v, l, r, 1, v }; }
inline void pushdown(int o, int l, int r) {
if (T[o].tg == -1)
return;
int mid = (l + r) >> 1;
tag(o << 1, l, mid, T[o].tg);
tag(o << 1 | 1, mid + 1, r, T[o].tg);
T[o].tg = -1;
}
void Build(int o, int l, int r) {
T[o] = (node) { 0, l, r, 1, -1 };
if (l == r)
return;
int mid = (l + r) >> 1;
Build(o << 1, l, mid);
Build(o << 1 | 1, mid + 1, r);
}
void Cover(int o, int l, int r, int L, int R, int v) {
if (L <= l && r <= R)
return tag(o, l, r, v);
pushdown(o, l, r);
int mid = (l + r) >> 1;
if (L <= mid)
Cover(o << 1, l, mid, L, R, v);
if (R > mid)
Cover(o << 1 | 1, mid + 1, r, L, R, v);
T[o] = T[o << 1] + T[o << 1 | 1];
}
node Query(int o, int l, int r, int L, int R) {
if (L <= l && r <= R)
return T[o];
pushdown(o, l, r);
int mid = (l + r) >> 1;
if (R <= mid)
return Query(o << 1, l, mid, L, R);
if (L > mid)
return Query(o << 1 | 1, mid + 1, r, L, R);
return Query(o << 1, l, mid, L, R) + Query(o << 1 | 1, mid + 1, r, L, R);
}
} seg;
set<pair<int, int> > st[100005];
signed main() {
freopen("block.in", "r", stdin);
freopen("block.out", "w", stdout);
seg.Build(1, 1, N);
n = read();
for (int i = 1; i <= n; i++) {
int l = read() + 1, r = read(), h = read();
node tmp = seg.Query(1, 1, N, l, r);
int ans = tmp.cs - 1, mx = tmp.mx;
int p1[2] = { l, r }, p2[2] = { l - 1, r + 1 };
for (auto v : { 0, 1 }) {
int x = p1[v], y = p2[v];
bool f = 0;
auto it = st[y].upper_bound(make_pair(mx, 0x3f3f3f3f));
while (it != st[y].end() && it -> first <= mx + h) {
++ans;
if (it -> second <= mx)
f = 1;
it = st[y].erase(it);
}
f |= (it != st[y].end() && it -> second <= mx);
int tm = seg.Query(1, 1, N, x, x).mx;
ans += (y && y <= N && !f && tm != mx && seg.Query(1, 1, N, y, y).mx >= mx);
if (tm != mx)
st[x].insert(make_pair(mx, tm + 1));
}
seg.Cover(1, 1, N, l, r, mx + h);
cout << ans << "\n";
}
return 0;
}
T3
现实
如果有两个点不相交的环,则无解。然后考虑抽出一个环来,考虑环上每个点不经过其他环上点能走到令一个环上点代表什么。将环上点按顺序重标号,若 \(x \rightarrow y\):\(x \le y\) 时,这相当于 \(ans \in [y, n] \cup [1, x]\),\(x \ge y\) 时,这相当于 \(ans \in [y, x]\)。对于第一种情况,只需要求出每个环上点不经过其他环上点能到达的最大的环上点。对于第二种情况,我们可以找到所有存在对应 \(y\) 的 \(x\) 最小值,这个最小值就是所有第二种情况的区间交的右端点。总之,一种实现方式需要求出每个环上点不经过其他环上点能到达的最大的环上点、最小的环上点以及是否存在一个 \(\ge\) 自己的环上点能够不经过其他环上点到达自己。前两个可以拓扑排序,最后一个考虑倒着扫每个环上点,bfs 扩展能扩展的点,遇到扩展过的就 continue,这样可以求出每个点第一次被扩展到是在加入哪一个环上点的时候。然后就可以判断了。
代码
#include <iostream>
#include <algorithm>
#include <string.h>
#include <vector>
#include <queue>
using namespace std;
#define getchar() p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++
char buf[1<<21], *p1, *p2, ch;
long long read() {
long long ret = 0, neg = 0; char c = getchar(); neg = (c == '-');
while (c < '0' || c > '9') c = getchar(), neg |= (c == '-');
while (c >= '0' && c <= '9') ret = ret * 10 + c - '0', c = getchar();
return ret * (neg ? -1 : 1);
}
const int inf = 0x3f3f3f3f;
inline void Cmax(int &x, int y) { x = max(x, y); }
inline void Cmin(int &x, int y) { x = min(x, y); }
int n, m;
int eu[1000005], ev[1000005];
int head[500005], nxt[1000005], to[1000005], ecnt;
inline void add(int u, int v) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt; }
vector<int> iG[500005];
int stk[500005], sz;
int vis[500005], inl[500005], V[500005];
int dfs(int x) {
if (V[x] | vis[x])
return V[x] * x;
vis[x] = V[x] = 1;
for (int t, i = head[x]; i; i = nxt[i]) {
if ((t = dfs(to[i])) != 0) {
(t > 0) ? (stk[++sz] = x, inl[x] = 1) : 0;
return (t == x ? -1 : t);
}
}
V[x] = 0;
return 0;
}
int odeg[500005];
int mn[500005], mx[500005], rec[500005];
int pr[500005], pre[500005];
queue<int> q;
int main() {
freopen("reality.in", "r", stdin);
freopen("reality.out", "w", stdout);
n = read(), m = read();
for (int i = 1; i <= m; i++) {
int &u = eu[i], &v = ev[i];
add(u = read(), v = read());
iG[v].emplace_back(u);
}
bool ok = 0;
for (int i = 1; i <= n && !ok; i++) ok |= dfs(i);
if (!ok) {
cout << n << "\n";
for (int i = 1; i <= n; i++) cout << i << " ";
return 0;
}
memset(vis, 0, sizeof vis);
reverse(stk + 1, stk + sz + 1);
for (int i = 1; i <= n; i++) mx[i] = -1, mn[i] = inf;
for (int i = 1; i <= sz; i++) {
pr[stk[i]] = i;
for (auto v : iG[stk[i]]) (v != stk[(i + sz - 2) % sz + 1]) ? (Cmax(mx[v], i), Cmin(mn[v], i)) : void();
}
for (int i = 1; i <= m; i++) odeg[eu[i]] += (!inl[eu[i]] && !inl[ev[i]]);
for (int i = 1; i <= n; i++) !odeg[i] && !inl[i] ? q.push(i) : void();
while (!q.empty()) {
int x = q.front(); q.pop();
vis[x] = 1;
for (int v : iG[x]) {
if (inl[x] && inl[v])
continue;
odeg[v] -= (!inl[x]);
Cmax(mx[v], mx[x]);
Cmin(mn[v], mn[x]);
(!odeg[v] && !inl[v]) ? q.push(v) : void();
}
}
for (int i = 1; i <= n; i++) ok &= (inl[i] | vis[i]);
if (!ok) {
cout << "0\n";
return 0;
}
memset(vis, 0, sizeof vis);
for (int i = sz; i; i--) {
for (int j = head[stk[i]]; j; j = nxt[j]) {
int v = to[j];
if (!vis[v] && pr[v] != pr[stk[i]] % sz + 1)
vis[v] = 1, rec[v] = i, (!inl[v] ? q.push(v) : void());
}
while (!q.empty()) {
int x = q.front();
q.pop();
for (int j = head[x]; j; j = nxt[j]) {
int v = to[j];
if (!vis[v])
vis[v] = 1, rec[v] = i, (!inl[v] ? q.push(v) : void());
}
}
vis[stk[i]] = 1;
}
int al = -1, ar = inf;
for (int i = 1; i <= sz; i++) {
mx[stk[i]] > i ? (++pre[i + 1], --pre[mx[stk[i]]]) : 0;
mn[stk[i]] <= i ? Cmin(ar, i) : void();
rec[stk[i]] ? Cmax(al, i) : void();
}
vector<int> ans;
for (int i = 1; i <= sz; i++) (!(pre[i] += pre[i - 1]) && al <= i && i <= ar) ? (ans.emplace_back(stk[i]), 0) : 0;
sort(ans.begin(), ans.end());
cout << ans.size() << "\n";
for (int v : ans) cout << v << " ";
cout << "\n";
return 0;
}