P4729 [HNOI2009] 积木游戏
P4729 [HNOI2009] 积木游戏
Solution
2023.09.06。八个月前做这个题调了六个小时。现在看来,除开欧拉定理的部分,整道题的思路极其清晰易懂,虽然码量大,但并不难码。尽管如此,融合了数据结构、图论(模型构建 + 三元环计数)、拓扑论(欧拉定理)多方面知识点,而且还有四面共角的细节问题,它仍然算得上是一道高位紫。
upd:原来有非图论做法。至少图论做法是真的逆天。
首先用数据结构维护,模拟出天降积木的过程,可以得到各块积木的上下边界的纵坐标,这部分的处理是独立的。
然后对具有 公共角 的的矩形(或地面)进行连边(可以对横纵分别考虑,双指针实现。注意建出的图不能包含重边!),容易证明边数是 \(O(n)\) 的(你可以对矩阵的四条边分别考虑)。
容易想到洞的数量与图上 简单环 的数量有一定的联系。
但这时并不能着急着将环数(以下的环数均表示简单环)当作洞数处理:比如三元环,以及如何处理四面共角。
给出一个统计的思路:数环的总数 \(\to\) 减去三元环的数量 \(\to\) 调整四面共角对正确答案的影响。
三元环计数是相对容易的,这里不赘述其 \(O(n\sqrt{n})\) 的做法。
数总环数可以使用平面图的欧拉定理:\(f + v - e = 2\)(面数 \(f\) 包含 \(1\) 个无穷平面)。注意由于存在四面共角的情况(即存在若干 \(K_4\)),原图并不能当作一个平面图,因此无法嵌入 \(S_0\) 进行考虑(也就是说,你并不能直接使用公式 \(f + v - e = 2\))。
给一个非平面图的例子:\(2 \times 3\) 个 \(K_4\) 拼接在一起,形成 \(3 \times 4\) 的点阵。
取边长为 \(3\) 的两边的中点,再在边长为 \(4\) 的两边各取中间四个点。
如果你圈出了这 \(6\) 个点组成的六边形,你不难发现,此处有一个图与 \(K_{3, 3}\) 同胚。
我们思考这个四面共角的情况对洞数的贡献是 没有任何意义的,于是可以将 \(K_4\) 转成四元环,考察各数值的变化量。具体如下考虑:
-
记原图为 \(G\)。删掉 \(G\) 中所有 \(K_4\) 的任意两条没有公共点的边 \(e_1, e_2\),每个 \(K_4\) 形成一个四元环。记改造的 \(K_4\) 数量为 \(c\),变换后的图为 \(G^{'}\)。易知 \(G^{'}\) 一定为平面图。
-
考察对 \(G\) 错误使用欧拉定理计算的答案 与 对 \(G^{'}\) 正确使用欧拉定理计算的答案(即正确答案)以及 \(G\) 与 \(G^{'}\) 相关量之间的联系。
-
记原图中非 \(K_4\) 内三元环数为 \(t\)。
-
记 \(G\) 中点数为 \(v\),边数为 \(e\),面数为 \(f\)(错误的算法下)。错误使用欧拉定理,得 \(三元环数 + 洞数 + 1 = f = e + 2 - v \xrightarrow{三元环数 = 4c + t} 洞数ans = e - v + 1 - t - 4c\)。
-
记 \(G^{'}\) 中点数为 \(v^{'} = v\),边数为 \(e^{'} = e - 2c\),面数为 \(f^{'}\)(正确的算法下)。正确使用欧拉定理,得 \(三元环数 + 四元环数 + 洞数 + 1 = f^{'} = e^{'} + 2 - v^{'} \xrightarrow{三元环数 = t, 四元环数 = c} 洞数ans^{'} = e - 2c + 2 - v - (c + 1) - t = e - v + 1 - t - 3c\)。
-
我们发现:\(ans^{'} - ans = c\)。不难猜测,每一个 \(K_4\) 在错误使用欧拉定理的算法下,都会少算 \(-1\) 的代价。事实上,如果你对每个 \(K_4\) 分别考虑,确实会得到这样的结论。不妨自己想一想。
于是,四面共角的偏移量居然如此简洁:只需要在原来的错误算法上,对每个 \(K_4\) 增加 \(1\) 个贡献。
最后回到原题,要求的是每落下一块积木对洞数造成的偏移量。
对此,我们只需要把贡献累计到与对应信息相关联的点中,出现时间最晚的那个即可。
从实现角度来讲,建边的过程是最痛苦的。
#include<bits/stdc++.h>
#define LL long long
#define DB double
#define MOD 1000000007
#define ls(x) x << 1
#define rs(x) x << 1 | 1
#define lowbit(x) x & (-x)
#define PII pair<int, int>
#define MP make_pair
#define VI vector<int>
#define VII vector<int>::iterator
#define all(x) x.begin(), x.end()
#define EB emplace_back
#define SI set<int>
#define SII set<int>::iterator
#define QI queue<int>
using namespace std;
template<typename T> void chkmn(T &a, const T &b) { (a > b) && (a = b); }
template<typename T> void chkmx(T &a, const T &b) { (a < b) && (a = b); }
int inc(const int &a, const int &b) { return a + b >= MOD ? a + b - MOD : a + b; }
int dec(const int &a, const int &b) { return a - b < 0 ? a - b + MOD : a - b; }
int mul(const int &a, const int &b) { return 1LL * a * b % MOD; }
int sqr(const int &a) { return 1LL * a * a % MOD; }
void Inc(int &a, const int &b) { ((a += b) >= MOD) && (a -= MOD); }
void Dec(int &a, const int &b) { ((a -= b) < 0) && (a += MOD); }
void Mul(int &a, const int &b) { a = 1LL * a * b % MOD; }
void Sqr(int &a) { a = 1LL * a * a % MOD; }
int qwqmi(int x, int k = MOD - 2)
{
int res = 1;
while(k)
{
if(k & 1) Mul(res, x);
Sqr(x), k >>= 1;
}
return res;
}
const int N = 1e5 + 5;
struct Rectangle
{
int l, r, bot, top, id;
}R1[N], R2[N];
int n;
struct SGT
{
struct SegTree
{
int l, r;
int v;
int tag;
}tr[N << 2];
void pushup(int p)
{
tr[p].v = max(tr[ls(p)].v, tr[rs(p)].v);
}
void build(int p, int l, int r)
{
tr[p].l = l;
tr[p].r = r;
tr[p].v = 0;
if(l == r)
return;
int mid = l + (r - l) / 2;
build(ls(p), l, mid);
build(rs(p), mid + 1, r);
}
void cal(SegTree &u, int v)
{
u.tag = v;
u.v = v;
}
void pushdown(int p)
{
if(tr[p].tag)
{
cal(tr[ls(p)], tr[p].tag);
cal(tr[rs(p)], tr[p].tag);
tr[p].tag = 0;
}
}
void modify(int p, int l, int r, int v)
{
if(tr[p].l >= l && tr[p].r <= r)
{
cal(tr[p], v);
return;
}
pushdown(p);
int mid = tr[p].l + (tr[p].r - tr[p].l) / 2;
if(mid >= l) modify(ls(p), l, r, v);
if(mid < r) modify(rs(p), l, r, v);
pushup(p);
}
int query(int p, int l, int r)
{
if(tr[p].l >= l && tr[p].r <= r)
return tr[p].v;
pushdown(p);
int mid = tr[p].l + (tr[p].r - tr[p].l) / 2;
int res = 0;
if(mid >= l) chkmx(res, query(ls(p), l, r));
if(mid < r) chkmx(res, query(rs(p), l, r));
return res;
}
}T;
vector<int> G[N]; int d[N];
map<PII, int> mp;
void add(int x, int y)
{
if(mp[MP(x, y)] || x == y)
return;
// printf("edge %d %d\n", x, y);
mp[MP(x, y)] = mp[MP(y, x)] = 1;
G[x].EB(y);
G[y].EB(x);
++d[x], ++d[y];
}
bool cmp(int x, int y)
{
return d[x] == d[y] ? x > y : d[x] > d[y];
}
int tag[N];
int ans[N];
int cnt;
struct node
{
int x, y, id;
}a[N << 2];
vector<int> to[N];
int main()
{
scanf("%d", &n);
T.build(1, 1, N - 5);
for(int i = 1; i <= n; ++i)
{
int l, r, h;
scanf("%d %d %d", &l, &r, &h);
int x = T.query(1, l + 1, r);
int bot = x, top = bot + h;
// printf("%d %d\n", bot, top);
T.modify(1, l + 1, r, top);
R2[i] = R1[i] = (Rectangle){l, r, bot, top, i};
if(bot == 0) add(0, i);
}
R1[0] = R2[0] = (Rectangle){-1, -1, -1, -1, 0};
// horizontally
sort(R1, R1 + n + 1, [&](Rectangle A, Rectangle B){
return A.l == B.l ? A.bot < B.bot : A.l < B.l;
});
sort(R2, R2 + n + 1, [&](Rectangle A, Rectangle B){
return A.r == B.r ? A.bot < B.bot : A.r < B.r;
});
for(int i = 1, j = 0; i <= n; ++i)
{
// printf("cas %d\n", i);
while(R2[j].r < R1[i].l) ++j;
while(R2[j].r == R1[i].l && R2[j].bot <= R1[i].top)
{
if(R2[j].top >= R1[i].bot)
add(R1[i].id, R2[j].id);
++j;
}
--j; if(j) --j;
// printf("horizontally %d %d\n", R1[i].id, R2[j].id);
}
// vertically
sort(R1, R1 + n + 1, [&](Rectangle A, Rectangle B){
return A.bot == B.bot ? A.l < B.l : A.bot < B.bot;
});
sort(R2, R2 + n + 1, [&](Rectangle A, Rectangle B){
return A.top == B.top ? A.l < B.l : A.top < B.top;
});
for(int i = 1, j = 0; i <= n; ++i)
{
// printf("cas %d\n", i);
while(R2[j].top < R1[i].bot) ++j;
while(R2[j].top == R1[i].bot && R2[j].l <= R1[i].r)
{
if(R2[j].r >= R1[i].l)
add(R1[i].id, R2[j].id);
++j;
}
--j; if(j) --j;
// printf("vertically %d %d\n", R1[i].id, R2[j].id);
}
for(int u = 0; u <= n; ++u)
{
for(auto v : G[u])
{
if(!cmp(u, v)) continue;
// printf("!!! %d %d\n", u, v);
ans[max(u, v)]++;
}
ans[u]--;
}
for(int u = 0; u <= n; ++u)
for(auto v : G[u])
{
if(d[u] > d[v] || (d[u] == d[v] && u > v))
to[u].EB(v);
}
for(int u = 0; u <= n; ++u)
{
for(auto v : to[u])
{
tag[v] = u + 1;
}
for(auto v : to[u])
{
for(auto w :to[v])
{
if(tag[w] == u + 1)
ans[max({u, v, w})]--;
}
}
}
for(int i = 1; i <= n; ++i)
{
a[++cnt] = (node){R1[i].l, R1[i].bot, R1[i].id};
a[++cnt] = (node){R1[i].l, R1[i].top, R1[i].id};
a[++cnt] = (node){R1[i].r, R1[i].bot, R1[i].id};
a[++cnt] = (node){R1[i].r, R1[i].top, R1[i].id};
}
sort(a + 1, a + cnt + 1, [&](node A, node B){
return A.x == B.x ? A.y < B.y : A.x < B.x;
});
for(int i = 1; i + 3 <= cnt; ++i)
{
PII p1 = MP(a[i].x, a[i].y);
PII p2 = MP(a[i + 1].x, a[i + 1].y);
PII p3 = MP(a[i + 2].x, a[i + 2].y);
PII p4 = MP(a[i + 3].x, a[i + 3].y);
if(p1 == p2 && p2 == p3 && p3 == p4)
ans[max({a[i].id, a[i + 1].id, a[i + 2].id, a[i + 3].id})]++;
}
for(int i = 1; i <= n; ++i)
printf("%d\n", ans[i]);
return 0;
}
// 4
// 1 2 1
// 2 3 1
// 1 2 1
// 2 3 1