P4729 [HNOI2009] 积木游戏

发布时间 2023-09-07 20:59:42作者: Schucking_Sattin

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