神奇 DS 题
P10061 [SNOI2024] 矩阵
由于旋转后矩阵内部的相对位置不变,所以只需要将外围的两圈共八行维护一下相对位置即可,使用十字链表维护,然后是大模拟。
我还是头一次使用指针写链表。
const int N = 3010, Mod1 = 998244353, Mod2 = 1e9 + 7;
int n, q, id[N][N];
LL b[N][N];
int dx[4] = {0, 1, 0, -1}, dy[4] = {1, 0, -1, 0};
struct Node { Node *ne[4]; LL d[4]; int id[4]; } a[N * N];
struct Vec { Node *o; LL v; int d; } v1[N], v2[N], v3[N], v4[N], v5[N], v6[N], v7[N], v8[N];
void init() {
for (int i = 1; i <= n; i ++) {
LL x = i + 1;
for (int j = 1; j <= n; j ++) b[i][j] = x, x = x * (i + 1) % Mod1;
}
for (int i = 0; i <= n; i ++)
for (int j = 0; j <= n; j ++) id[i][j] = i * (n + 1) + j + 1;
for (int i = 0; i <= n; i ++)
for (int j = 0; j <= n; j ++) {
Node *o1 = a + id[i][j];
for (int p = 0; p < 4; p ++) {
int x = (i + dx[p] + n + 1) % (n + 1), y = (j + dy[p] + n + 1) % (n + 1);
o1->ne[p] = a + id[x][y], o1->d[p] = b[x][y] - b[i][j], o1->id[p] = p;
}
}
}
// 点,角度,值,方向
void run(Node *&o, int &d, LL &v, int op) {
int p = (op + d) % 4;
v += o->d[p], d = (o->id[p] - op + 4) % 4, o = o->ne[p];
}
void getrow(Vec *vec, int i) {
Node *o = a + 1; int d = 0; LL v = 0;
for (int j = 1; j <= i; j ++) run(o, d, v, 1);
for (int j = 1; j <= n; j ++) run(o, d, v, 0), vec[j] = {o, v, d};
}
void getcol(Vec *vec, int i) {
Node *o = a + 1; int d = 0; LL v = 0;
for (int j = 1; j <= i; j ++) run(o, d, v, 0);
for (int j = 1; j <= n; j ++) run(o, d, v, 1), vec[j] = {o, v, d};
}
void getr(Vec *v1, Vec *v2, int i, int l, int r) {
getrow(v1, i);
for (int j = l; j <= r; j ++) v2[j] = v1[j], run(v2[j].o, v2[j].d, v2[j].v, 1);
}
void getc(Vec *v1, Vec *v2, int i, int l, int r) {
getcol(v1, i);
for (int j = l; j <= r; j ++) v2[j] = v1[j], run(v2[j].o, v2[j].d, v2[j].v, 0);
}
void change(Node *o1, Node *o2, LL v1, LL v2, int d1, int d2, int op) {
int p1 = (op + d1) % 4, p2 = (op + d2 + 2) % 4;
o1->ne[p1] = o2, o1->d[p1] = v2 - v1, o1->id[p1] = (p2 + 2) % 4;
o2->ne[p2] = o1, o2->d[p2] = v1 - v2, o2->id[p2] = (p1 + 2) % 4;
}
void work(Vec &v1, Vec &v2, LL cv, int cd, int op) {
change(v1.o, v2.o, v1.v + cv, v2.v, (v1.d + cd) % 4, v2.d, op);
}
void add(int xl, int yl, int xr, int yr, LL d) {
getr(v1, v2, xl - 1, yl, yr); getr(v3, v4, xr, yl, yr);
getc(v5, v6, yl - 1, xl, xr); getc(v7, v8, yr, xl, xr);
for (int i = yl; i <= yr; i ++) work(v2[i], v1[i], d, 0, 3);
for (int i = yl; i <= yr; i ++) work(v3[i], v4[i], d, 0, 1);
for (int i = xl; i <= xr; i ++) work(v6[i], v5[i], d, 0, 2);
for (int i = xl; i <= xr; i ++) work(v7[i], v8[i], d, 0, 0);
}
void rotate(int xl, int yl, int xr, int yr) {
getr(v1, v2, xl - 1, yl, yr); getr(v3, v4, xr, yl, yr);
getc(v5, v6, yl - 1, xl, xr); getc(v7, v8, yr, xl, xr);
for (int i = xl; i <= xr; i ++) work(v2[yr - i + xl], v5[i], 0, 1, 2);
for (int i = xl; i <= xr; i ++) work(v3[yr - i + xl], v8[i], 0, 1, 0);
for (int i = yl; i <= yr; i ++) work(v6[xl + i - yl], v4[i], 0, 1, 1);
for (int i = yl; i <= yr; i ++) work(v7[xl + i - yl], v1[i], 0, 1, 3);
}
// void output() {
// LL ans = 0;
// for (int i = 1; i <= n; i ++) {
// getrow(v1, i);
// for (int j = 1; j <= n; j ++)
// ans = (ans + 1ll * v1[j].v * qmi(12345, (i - 1) * n + j, Mod2) % Mod2) % Mod2;
// // ans += v1[j].v;
// }
// cout << ans << endl;
// }
signed main() {
n = read(), q = read(); init();
// output();
while (q --) {
int op = read(), xl = read(), yl = read(), xr = read(), yr = read();
if (op == 1) rotate(xl, yl, xr, yr);
else {
LL d = read();
add(xl, yl, xr, yr, d);
}
// output();
}
LL ans = 0, mul = 12345;
for (int i = 1; i <= n; i ++) {
getrow(v1, i);
for (int j = 1; j <= n; j ++)
ans = (ans + v1[j].v % Mod2 * mul % Mod2) % Mod2, mul = mul * 12345 % Mod2;
// ans += v1[j];
}
printf("%lld\n", ans);
return 0;
}
CF571D Campus
显然要使用并查集,那我们要对并查集维护一些标记。一个技巧是并查集如果要维护标记那就不要路径压缩直接按秩合并。
对于并查集一我们每次增加对并查集的根维护 \((add, t)\) 表示增加的标记,对于并查集二记录一下清零的时间。那么询问时我们不断向根走找到最近的一次清零然后把这次清零之后的标记加起来即可。
const int N = 500010;
int n, m, cls[N];
vector<PII> add[N];
struct DSU {
int fa[N], sz[N], t[N];
void init(int n) {
for (int i = 1; i <= n; i ++) fa[i] = i, sz[i] = 1;
}
int find(int x) {
while (fa[x] != x) x = fa[x];
return x;
}
void merge(int x, int y, int k) {
x = find(x), y = find(y);
if (sz[x] < sz[y]) swap(x, y);
fa[y] = x, sz[x] += sz[y], t[y] = k;
}
} dsu1, dsu2;
signed main() {
n = read(), m = read();
dsu1.init(n), dsu2.init(n);
for (int i = 1; i <= n; i ++) add[i].push_back({-1, 0});
for (int i = 1; i <= m; i ++) {
char op[2]; int x;
scanf("%s%lld", op, &x);
if (*op == 'U') {
int y = read();
dsu1.merge(x, y, i);
} else if (*op == 'M') {
int y = read();
dsu2.merge(x, y, i);
} else if (*op == 'A') {
int f = dsu1.find(x);
add[f].push_back({i, dsu1.sz[f] + add[f].back().second});
} else if (*op == 'Z') {
int f = dsu2.find(x);
cls[f] = i;
} else {
int t = cls[x], tx = x;
while (x != dsu2.fa[x]) {
if (cls[dsu2.fa[x]] > dsu2.t[x]) t = max(t, cls[dsu2.fa[x]]);
x = dsu2.fa[x];
}
x = tx;
int p = lower_bound(add[x].begin(), add[x].end(), make_pair(t, 0ll)) - add[x].begin();
int ans = add[x].back().second - add[x][p - 1].second;
while (x != dsu1.fa[x]) {
int f = dsu1.fa[x];
p = lower_bound(add[f].begin(), add[f].end(), make_pair(max(t, dsu1.t[x]), 0ll)) - add[f].begin();
ans += add[f].back().second - add[f][p - 1].second; x = f;
}
printf("%lld\n", ans);
}
}
return 0;
}