T1、迷宫
一个 $ n imes m (n leq 5, m leq 10 ^ 5) $ 的矩阵,(0) 表示格子不能走,(1) 表示格子可以走,只能向上、下、右三个方向走。有 $ q (q leq 5 imes 10 ^ 4) $ 次操作,操作有两种:
1、修改某个格子的类型;
2、查询从 $ (a, b) $ 到 ((c, d)) 的最短路,不可达输出 (-1)。 (保证后者在前者右边)。
(Sol):
观察到 (n leq 5),这是很重要的提示。
在左右方向上建线段树,线段树每个叶子节点为一个矩阵,该矩阵描述了这一列上点的连通关系,特别地,若该列某一行的格子不能走,它到自己在矩阵中应表示为不可到达;
合并时枚举中间点 (n ^ 3) 暴力合并。
然而我考场上写了常数较大的做法:每个叶子节点的矩阵描述这一列到下一列的连通关系,合并同理;这样写修改时要改两个点,然后就没了。
(Source):
#include <cstdio>
#include <cstring>
#include <algorithm>
int in() {
int x = 0; char c = getchar(); bool f = 0;
while (c < '0' || c > '9')
f |= c == '-', c = getchar();
while (c >= '0' && c <= '9')
x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
return f ? -x : x;
}
const int N = 2e5 + 5;
int n, m, q, mp[7][N];
template<typename T>inline void chk_min(T &_, T __) {
_ = !~_ ? __ : (_ < __ ? _ : __);
}
struct node {
int a[6][6];
node () {
memset(a, -1, sizeof(a));
}
inline int* operator [] (const int x) {
return a[x];
}
inline node operator + (node b) const {
node ret;
for (int k = 1; k <= n; ++k)
for (int i = 1; i <= n; ++i)
if (~a[i][k])
for (int j = 1; j <= n; ++j)
if (~b[k][j])
chk_min(ret[i][j], a[i][k] + b[k][j] + 1);
return ret;
}
} ;
struct segment_tree {
node t[N << 2];
void init(int x, int p) {
for (int i = 1, j; i <= n; ++i) {
for (j = i; j <= n && mp[j][x]; ++j)
t[p][i][j] = j - i;
for (; j <= n; ++j)
t[p][i][j] = -1;
for (j = i; j && mp[j][x]; --j)
t[p][i][j] = i - j;
for (; j; --j)
t[p][i][j] = -1;
}
}
inline void push_up(int p) {
t[p] = t[p << 1] + t[p << 1 | 1];
}
void build(int tl, int tr, int p) {
if (tl == tr)
return init(tl, p);
int mid = (tl + tr) >> 1;
build(tl, mid, p << 1), build(mid + 1, tr, p << 1 | 1);
push_up(p);
}
void modify(int pos, int tl, int tr, int p) {
if (tl == tr)
return init(tl, p);
int mid = (tl + tr) >> 1;
if (mid >= pos)
modify(pos, tl, mid, p << 1);
else
modify(pos, mid + 1, tr, p << 1 | 1);
push_up(p);
}
node query(int l, int r, int tl, int tr, int p) {
if (l <= tl && tr <= r)
return t[p];
int mid = (tl + tr) >> 1;
if (mid < l)
return query(l, r, mid + 1, tr, p << 1 | 1);
if (mid >= r)
return query(l, r, tl, mid, p << 1);
return query(l, r, tl, mid, p << 1) +
query(l, r, mid + 1, tr, p << 1 | 1);
}
} T;
int main() {
//freopen("in", "r", stdin);
freopen("maze.in", "r", stdin);
freopen("maze.out", "w", stdout);
n = in(), m = in(), q = in();
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
mp[i][j] = in();
T.build(1, m, 1);
int a, b, c, d;
node res;
while (q--) {
if (in() == 1) {
a = in(), b = in();
mp[a][b] ^= 1;
T.modify(b, 1, m, 1);
} else {
a = in(), b = in(), c = in(), d = in();
res = T.query(b, d, 1, m, 1);
printf("%d
", res[a][c]);
}
}
return 0;
}
T2、猛汉王
平面上有 (n (n leq 10 ^ 5)) 个黑点,(m (n leq 10 ^ 5)) 个白点,若黑点 (x) 与白点 (y) 的曼哈顿距离小于等于 (d (d leq 10 ^ 9)),则 (x) 向 (y) 连边,否则 (y) 向 (x) 连边。
同色点之间的连边方向不确定,求至少有一个黑点和白点的三元环的最小值和最大值。
(Sol):
显然地,三元环上有两个同色点。
由于我们不关心异色边是怎么连的,只关心是否能为同向环上的边,所以黑色点和白色点同理。
记 (cover(x)) 表示 (x) 能覆盖的白色点,(cover ( x, y )) 表示 (x, y) 共同覆盖到的白色点。
以下以最大值为例 (最小值同理)。
只要求出 (cover(x)) 即可;
扫描线维护以下就行了。
(Source):
#include <cstdio>
#include <cstring>
#include <algorithm>
int in() {
int x = 0; char c = getchar(); bool f = 0;
while (c < '0' || c > '9')
f |= c == '-', c = getchar();
while (c >= '0' && c <= '9')
x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
return f ? -x : x;
}
template<typename T>inline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
template<typename T>inline void chk_max(T &_, T __) { _ = _ > __ ? _ : __; }
const int N = 1e5 + 5;
struct node {
int x, y;
} a[N], b[N];
struct options {
long long x; int y, z, id;
int typ;
} opt[N * 3];
int n, m, d, nn, cntx[N], cnty[N];
long long ans_min, ans_max;
long long tmp[N * 3];
inline bool cmpx(const options &i, const options &j) {
if (i.x == j.x)
return i.typ == 0 ? 1 : j.typ != 0;
return i.x < j.x;
}
void prep() {
nn = 0;
for (int i = 1; i <= m; ++i)
tmp[i] = b[i].y;
for (int i = 1; i <= n; ++i)
tmp[i + m] = 1ll * a[i].y - d;
for (int i = 1; i <= n; ++i)
tmp[i + n + m] = 1ll * a[i].y + d;
std::sort(tmp + 1, tmp + 1 + n + n + m);
nn = std::unique(tmp + 1, tmp + 1 + n + n + m) - tmp - 1;
for (int i = 1; i <= m; ++i) {
opt[i].typ = opt[i].id = 0;
opt[i].x = b[i].x;
opt[i].y = std::lower_bound(tmp + 1, tmp + 1 + nn, b[i].y) - tmp;
opt[i].z = 0;
}
for (int i = 1; i <= n; ++i) {
opt[i + m].typ = -1;
opt[i + m].id = i;
opt[i + m].x = 1ll * a[i].x - d - 1;
opt[i + n + m].typ = 1;
opt[i + n + m].id = i;
opt[i + n + m].x = 1ll * a[i].x + d;
opt[i + m].y = opt[i + n + m].y = std::lower_bound(tmp + 1, tmp + 1 + nn, 1ll * a[i].y - d) - tmp;
opt[i + m].z = opt[i + n + m].z = std::lower_bound(tmp + 1, tmp + 1 + nn, 1ll * a[i].y + d) - tmp;
}
std::sort(opt + 1, opt + 1 + n + n + m, cmpx);
}
struct binary_index_tree {
int a[N * 3];
inline void init() { memset(a, 0, sizeof(a)); }
void insert(int p, int k) { for (; p <= nn; p += (p & -p)) a[p] += k; }
int ask(int p, int ret = 0) { for (; p; p -= (p & -p)) ret += a[p]; return ret; }
} bit;
void work() {
prep();
bit.init();
for (int i = 1; i <= n + n + m; ++i) {
if (!opt[i].typ) {
bit.insert(opt[i].y, 1);
} else {
cntx[opt[i].id] += opt[i].typ * (bit.ask(opt[i].z) - bit.ask(opt[i].y - 1));
}
}
}
int main() {
//freopen("in", "r", stdin);
freopen("mhw.in", "r", stdin);
freopen("mhw.out", "w", stdout);
n = in(), m = in(), d = in();
for (int i = 1; i <= n; ++i) {
a[i] = (node){in(), in()};
a[i] = (node){a[i].x + a[i].y, a[i].x - a[i].y};
}
for (int i = 1; i <= m; ++i) {
b[i] = (node){in(), in()};
b[i] = (node){b[i].x + b[i].y, b[i].x - b[i].y};
}
work();
std::swap(a, b);
std::swap(n, m);
std::swap(cntx, cnty);
work();
std::sort(cntx + 1, cntx + 1 + n);
std::sort(cnty + 1, cnty + 1 + m);
for (int i = 1; i <= n; ++i) {
ans_min += 1ll * cntx[i] * (n - i);
ans_min -= 1ll * cntx[i] * (cntx[i] - 1) / 2;
ans_max += 1ll * cntx[i] * (i - 1);
ans_max -= 1ll * cntx[i] * (cntx[i] - 1) / 2;
}
for (int i = 1; i <= m; ++i) {
ans_min += 1ll * cnty[i] * (m - i);
ans_min -= 1ll * cnty[i] * (cnty[i] - 1) / 2;
ans_max += 1ll * cnty[i] * (i - 1);
ans_max -= 1ll * cnty[i] * (cnty[i] - 1) / 2;
}
printf("%lld %lld
", ans_min, ans_max);
return 0;
}
T3、工厂
给定一个二分图,可以加边,使得二分图的所有极大匹配都是完美匹配。
(Sol):
先证明一件事:当且仅当这个二分图里的每个联通块左右点数相同且边都是连满的 ((A)),满足题意 ((B))。
由 (B) 到 (A) 是显然的,不再赘述;
由 (A) 到 (B) 可以用反证法:
若联通块中有两个异侧点 (a),(b) 之间没有连边,找到一条从 (a) 到 (b) 的路径,把奇数边加入匹配边集,在此基础上找到一个极大匹配,找到的应该是完美匹配;将路径上奇数边取出匹配边集,偶数边加入,此时也是一个极大匹配但不是完美匹配。
(QED)
将几个联通块合并的代价为 (( sum x_i ) ^ 2)。
记 (f_{s,i}) 表示当前选出联通块集合为 (s),已经安排了点数和为 (2i) 的联通块 (使它们满足上述条件) 的最小代价。
转移有两种:
由于状态数太多,这样也过不了,可以优化状压的方式;
我们只关心每一种联通块 (以左右点数分类) 有几个,可以用变进制数来优化状态数;
出题人告诉我们最多有 (172032) 种状态。
(Source):
#include <cstdio>
#include <cstring>
#include <algorithm>
int in() {
int x = 0; char c = getchar(); bool f = 0;
while (c < '0' || c > '9')
f |= c == '-', c = getchar();
while (c >= '0' && c <= '9')
x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
return f ? -x : x;
}
template<typename T>inline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
template<typename T>inline void chk_max(T &_, T __) { _ = _ > __ ? _ : __; }
const int N = 35, inf = 0x3f3f3f3f;
int n, m, mp[N << 1][N << 1], tmp_x, tmp_y, init_edge, buc[N][N], pre[N << 1], f[172033][N], num[N << 1];
bool vis[N << 1];
struct node {
int x, y;
inline bool operator < (const node &b) const {
return buc[this->x][this->y] < buc[b.x][b.y];
}
inline bool operator == (const node &b) const {
return this->x == b.x && this->y == b.y;
}
} a[N + N];
void prep(const int u) {
vis[u] = 1;
if (u <= n)
++tmp_x;
else
++tmp_y;
for (int i = 1; i <= mp[u][0]; ++i)
if (!vis[mp[u][i]])
prep(mp[u][i]);
}
int main() {
//freopen("in", "r", stdin);
freopen("factory.in", "r", stdin);
freopen("factory.out", "w", stdout);
n = in();
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j) {
char c = getchar();
while (c < '0' || c > '1')
c = getchar();
if (c == '1') {
mp[i][++mp[i][0]] = j + n;
mp[j + n][++mp[j + n][0]] = i;
}
init_edge += c == '1';
}
for (int i = 1; i <= n + n; ++i)
if (!vis[i]) {
tmp_x = tmp_y = 0;
prep(i);
++buc[tmp_x][tmp_y];
}
for (int i = 0; i <= n; ++i)
for (int j = 0; j <= n; ++j)
if (buc[i][j])
a[++m] = (node){i, j};
std::sort(a + 1, a + 1 + m);
m = std::unique(a + 1, a + 1 + m) - a - 1;
pre[1] = 1;
for (int i = 2; i <= m + 1; ++i)
pre[i] = pre[i - 1] * (buc[a[i - 1].x][a[i - 1].y] + 1);
memset(f, inf, sizeof(f));
f[0][0] = 0;
int nowx, nowy;
for (int s = 0; s < (pre[m + 1]); ++s) {
nowx = nowy = 0;
for (int i = 1; i <= m; ++i)
num[i] = s % pre[i + 1] / pre[i];
for (int i = 1; i <= m; ++i)
nowx += num[i] * a[i].x, nowy += num[i] * a[i].y;
if (nowx == nowy)
for (int i = 0; i < nowx; ++i)
chk_min(f[s][nowx], f[s][i] + (nowx - i) * (nowx - i));
for (int i = 1; i <= m; ++i)
if (num[i] < buc[a[i].x][a[i].y])
for (int j = 0; j <= n; ++j)
chk_min(f[s + pre[i]][j], f[s][j]);
}
printf("%d
", f[pre[m + 1] - 1][n] - init_edge);
return 0;
}