线段树优化 dp && 扫描线

Part1. 线段树优化 dp

CF115E

\(f[i]\) 表示前 \(i\) 条路中修复若干条路后可以赚到的最多的钱。

不修第 \(i\) 条路时 \(f[i]=f[i-1]\)

区间 \([j+1,i]\) 中的路都修好时有 \(f[i] = \max \limits_{0≤ j <i} \{f[j]+\operatorname{val}(j+1,i)-\operatorname{cost}(j+1,i)\}\)\(\operatorname{cost}(j+1,i)\) 表示修好区间中所有路所要花的钱;\(\operatorname{val}(j+1,i)\) 表示区间中的路都修好的情况下,举行比赛可以赚到的钱。

线段树优化,第 \(j\) 个位置 \(vec[j]\) 储存 \(f[j]+\operatorname{val}(j+1,i)-\operatorname{cost}(j+1,i)\)

枚举 \(i\),将 \(vec\) 减去修路的价钱,因为都要多修一条路。对于每个右端点 \(r=i\) 的比赛 \((l,r,v)\),将 \(c[0\dots l-1]\) 都加上 \(v\)。在这些状态中,\([l,r]\) 区间的路都修好可以进行比赛。更新状态 \(f[i]=\max(f[i-1],\max\limits_{0≤ j <i}\{c[j]\})\)。更新 \(vec[i]\)

复杂度\(\operatorname{O}(n\log n)\)

int n, m;
int v[N];
int f[N];
struct add 
{
	int l, k;
	add(int p, int q) 
	{
		l = p, k = q;
	}
};
vector<add> vec[N];

struct Node 
{
	int v, tag;//v is max
} t[N * 4];

#define ls p << 1
#define rs p << 1 | 1

void push_up(int p) 
{
	t[p].v = max(t[ls].v, t[rs].v);
}

void push_down(int p) 
{
	t[ls].v += t[p].tag, t[ls].tag += t[p].tag;
	t[rs].v += t[p].tag, t[rs].tag += t[p].tag;
	t[p].tag = 0;
}

void change(int p, int l, int r, int x, int y, int d) 
{
	if (l >= x && r <= y)
	{
		t[p].v += d;
		t[p].tag += d;
		return ;
	} 
	push_down(p);
	int mid = (l + r) >> 1;
	if (x <= mid) change(ls, l, mid, x, y, d);
	if (y > mid) change(rs, mid + 1, r, x, y, d);
	push_up(p);
}

int query(int p, int l, int r, int x, int y) 
{
	if (l >= x && r <= y) return t[p].v;
	push_down(p);
	int mid = (l + r) >> 1;
	int res = 0;
	if (x <= mid) res = max(res, query(ls, l, mid, x, y));
	if (y > mid) res = max(res, query(rs, mid + 1, r, x, y));
	return res;
}

signed main() 
{
	cin >> n >> m;
	for (rint i = 1; i <= n; i++) cin >> v[i];
	for (rint i = 1; i <= m; i++) 
	{
		int x, y, z;
		cin >> x >> y >> z;
		vec[y].push_back(add(x, z));
	}	
	for (rint i = 1; i <= n; i++) 
	{
		change(1, 0, n, 0, i - 1, -v[i]);
		for (rint j = 0; j < vec[i].size(); j++) 
		{
			int ll = vec[i][j].l, kk = vec[i][j].k;
			change(1, 0, n, 0, ll - 1, kk);
		}
		f[i] = max(query(1, 0, n, 0, i - 1), f[i - 1]);
		change(1, 0, n, i, i, f[i]);
	}
	cout << f[n] << endl;
	return 0;
}

CF474E Pillars

\(f_i\) 表示以 \(a_i\) 结尾的满足条件的最长子序列

\[f_{i}=\max_{j<i,|b_i-b_j|≥ d}\{f_j\}+1 \]

\(|b_i-b_j|\geq d\) 考虑这个限制条件每次转移需要用最大值,用线段树维护来优化。每次在线段树上下标为 \(a_i\) 处,拆入 \(f_i\) 。查询操作:我们把绝对值拆开 \(b_j\leq b_i-d\)\(b_j \geq b_i+d\),等价成询问区间最大值。

方案则在线段树上再维护一个区间最大值的位置,求 \(f_i\) 时记录前驱即可

struct Node 
{
	int v, pos;//v is max
	Node(){v = 0, pos = 0;};
	bool friend operator < (Node x, Node y)
	{
		return x.v < y.v;
	}	
} t[M * 4];

#define ls p << 1
#define rs p << 1 | 1

void push_up(int p) 
{
	t[p] = max(t[ls], t[rs]);
}

void change(int p, int l, int r, int x, int v, int d) 
{
	if (l == r) 
	{
		if (v > t[p].v) t[p].v = v, t[p].pos = d;
		return ;
	}
	int mid = (l + r) >> 1;
	if (x <= mid) change(ls, l, mid, x, v, d);
	else change(rs, mid + 1, r, x, v, d);
	push_up(p);
}

Node query(int p, int l, int r, int x, int y) 
{
	if (l >= x && r <= y) return t[p];
	int mid = (l + r) >> 1;
	Node res;
	if (x <= mid) res = max(res, query(ls, l, mid, x, y));
	if (y > mid) res = max(res, query(rs, mid + 1, r, x, y));
	return res;
}

void print(int x) 
{
	while (x) 
	{
		ans[++cnt] = x;
		x = pre[x];
	}
	for (rint i = cnt; i >= 1; i--) 
	{
		cout << ans[i] << " ";
	}
}

signed main() 
{
	cin >> n >> d;
	for (rint i = 1; i <= n; i++) 
	{
		cin >> b[i];
		a[i] = b[i];
		b[i + n] = a[i + n] = a[i] - d;
		b[i + 2 * n] = a[i + 2 * n] = a[i] + d;
	}
	sort(b + 1, b + 3 * n + 1);
	int tot = unique(b + 1, b + 3 * n + 1) - b - 1;
	for (rint i = 1; i <= 3 * n; i++) a[i] = lower_bound(b + 1, b + tot + 1, a[i]) - b;
	f[1] = 1;
	change(1, 1, tot, a[1], 1, 1);
	for (rint i = 2; i <= n; i++) 
	{
		Node x, y, z;
		x = query(1, 1, tot, 1, a[i + n]);
		y = query(1, 1, tot, a[i + 2 * n], tot);
		z = max(x, y);
		f[i] = z.v + 1;
		pre[i] = z.pos;
		change(1, 1, tot, a[i], f[i], i);
	}
	for (rint i = 1; i <= n; i++) 
	{
		if (maxx < f[i]) maxx = f[i], id = i;
	}
	cout << maxx << endl;
	print(id);
	return 0;
}

[POI2005] AUT-The Bus

\(f[i][j]\) 表示从 \((1,1)\) 走到 \((i,j)\)\(max\)

\(f[i][j] = max_{k \le i \wedge l \le j}\{f[k][l]\} + val_{i,j}\)

横纵坐标只能递增,显然二位偏序,在以当前点为右下角的矩形中查一个最大值出来转移,只需要选一个坐标为第一关键字排个序即可

每次 query 时, 出现在 \([1, x_i]\) 范围内的最大值一定已经是之前做过的合法解

int n, m, k;
int ans;
int b[N];
struct node
{
    int x, y, v;
	friend bool operator < (node a, node b)
	{
		return (a.y == b.y) ? (a.x < b.x) : (a.y < b.y);
	}	
}p[M];
struct Node 
{
	int v;//v is max
	Node(){v = 0;};	
} t[M * 4];

#define ls p << 1
#define rs p << 1 | 1

void push_up(int p) 
{
	t[p].v = max(t[ls].v, t[rs].v);
}

void change(int p, int l, int r, int x, int v) 
{
	if (l == r) 
	{
		if (v > t[p].v) t[p].v = v;
		return ;
	}
	int mid = (l + r) >> 1;
	if (x <= mid) change(ls, l, mid, x, v);
	else change(rs, mid + 1, r, x, v);
	push_up(p);
}

int query(int p, int l, int r, int x, int y) 
{
	if (l >= x && r <= y) return t[p].v;
	int mid = (l + r) >> 1;
	int res = 0;
	if (x <= mid) res = max(res, query(ls, l, mid, x, y));
	if (y > mid) res = max(res, query(rs, mid + 1, r, x, y));
	return res;
}

signed main()
{
	cin >> n >> m >> k;
	for (rint i = 1; i <= k; i++)
	{
		cin >> p[i].x >> p[i].y >> p[i].v;
		b[i] = p[i].x;
	}
	b[k + 1] = 0;
	sort(b + 1, b + 2 + k);
	int tot = unique(b + 1, b + 2 + k) - (b + 1);
	sort(p + 1, p + 1 + k);
	for (rint i = 1; i <= k; i++)
	{
		p[i].x = lower_bound(b + 1, b + 1 + tot, p[i].x) - b;
		int ret = query(1, 1, tot, 1, p[i].x);
		ret += p[i].v;
		change(1, 1, tot, p[i].x, ret);
	}
	cout << query(1, 1, tot, 1, tot) << endl;
	return 0;
}

CF833B The Bakery

\(f_{i,j}\) 表示前 \(i\) 个数分为 \(j\) 段的最大价值

\(f_{i,j}=\max_{k=0}^{i-1} f_{k,j-1}+cost(k+1,i)\)

设位置为 \(i\),表示的数为 \(a_i\)\(pre_{a_i}\) 记录 \(a_i\) 的前驱, 那么 \(a_i\) 的贡献就只有 \([pre_{a_i}+1,i]\) 这些位置上的数有贡献。

用线段树进行区间修改操作和查询最大值的操作

int n, m;
int a[N];
int f[N][M], pre[N], id[N];
bool vis[N];
int p[N];

struct Segment_Tree 
{	
	struct node 
	{
		int l, r;
		int v, tag;
	} t[4 * N];
    
	#define ls p << 1
	#define rs p << 1 | 1
		
	void push_up(int p)
	{
        t[p].v = max(t[ls].v, t[rs].v);		
	}
	
	void push_down(int p) 
	{
		t[ls].v += t[p].tag, t[rs].v += t[p].tag;
		t[ls].tag += t[p].tag, t[rs].tag += t[p].tag;
		t[p].tag = 0;
	}
	
	void clear(int i, int p, int l, int r) 
	{
		t[p].l = l;
		t[p].r = r;
		if (l == r) 
		{
			t[p].v = f[l][i];
			return ;
		}
		int mid = (l + r) >> 1;
		clear(i, ls, l, mid);
		clear(i, rs, mid + 1, r);
	}
	
	void change(int p, int l, int r, int x) 
	{
		if (t[p].l >= l && t[p].r <= r) 
		{
			t[p].tag += x;
			t[p].v += x;
			return ;
		}
		push_down(p);
		int mid = (t[p].l + t[p].r) >> 1;
		if (l <= mid) change(ls, l, r, x);
		if (r > mid) change(rs, l, r, x);
		push_up(p);
	}
	
	int query(int p, int l, int r) 
	{
		if (t[p].l >= l && t[p].r <= r) return t[p].v;
		push_down(p);
		int mid = (t[p].l + t[p].r) >> 1;
		int res = 0;
		if (l <= mid) res = query(ls, l, r);
		if (r > mid) res = max(res, query(rs, l, r));
		return res;
	}
} tr[M];

signed main() 
{
	cin >> n >> m;
	
	for (rint i = 1; i <= n; i++) 
	{
		cin >> a[i];
		pre[i] = id[a[i]];
		id[a[i]] = i;
	}
	
	for (rint i = 1; i <= n; i++) 
	{
		f[i][1] = f[i - 1][1];
		if (!vis[a[i]]) 
		{
			vis[a[i]] = 1;
			f[i][1]++;
		}
	}
	
	for (rint j = 2; j <= m; j++) 
	{	
		tr[j - 1].clear(j - 1, 1, 0, n);
		for (rint i = 1; i <= n; i++)
		{
			tr[j - 1].change(1, pre[i], i - 1, 1);
			f[i][j] = tr[j - 1].query(1, 0, i - 1);
		}
	}
	
	cout << f[n][m] << endl;
	
	return 0;
}

[NOIP2023] 天天爱打卡

\(m\) 个区间 \([l_i,r_i]\),每个区间有一个权值 \(w_i\),选一个点需要花费 \(d\) 的代价,但选满一个区间后可以得到 \(w_i\) 的代价,任意长为 \(k+1\) 的一段不能全被选,求最大代价。

\(f_i\) 表示前 \(i\) 天的最大答案

\(f_{i}=\max(f_{i-1},\max\limits_{j=i-k-1}^{i-1}\{f_{j}-(i-j-1)d+w(j+1,i-1)\),其中 \(w(L,R)\) 表示 \(L≤l_i≤r_i≤R\)\(\sum v_i\)

如果要恰好包含一些区间,而不浪费多选点的话,可能的转移点只会是所有的 \(l_i-1,r_i+1\) 。线段树优化。

int n, m;
int k, d;
int l[N], r[N], w[N], b[M], f[M];
vector<int> v[M];

struct Node 
{
	int v, tag;
} t[M];

#define ls p << 1
#define rs p << 1 | 1

void push_up(int p) 
{
	t[p].v = max(t[ls].v, t[rs].v);
}

void push_down(int p) 
{
	t[ls].tag += t[p].tag, t[rs].tag += t[p].tag;
	t[ls].v += t[p].tag, t[rs].v += t[p].tag;
	t[p].tag = 0;
}

void build(int p, int l, int r) 
{
	t[p].tag = 0;
	if (l == r) 
	{
		t[p].v = 0;
		return;
	}
	int mid = (l + r) >> 1;
	build(ls, l, mid);
	build(rs, mid + 1, r);
	push_up(p);
}

void change(int p, int l, int r, int x, int y, int d) 
{
	if (x <= l && r <= y) 
	{
		t[p].v += d, t[p].tag += d;
		return;
	}
	int mid = (l + r) >> 1;
	push_down(p);
	if (x <= mid) change(ls, l, mid, x, y, d);
	if (y > mid) change(rs, mid + 1, r, x, y, d);
	push_up(p);
}

int query(int p, int l, int r, int x, int y) 
{
	if (x > y) return -inf;
	if (x <= l && r <= y) return t[p].v;
	push_down(p);
	int mid = (l + r) >> 1;
	int res = -inf;
	if (x <= mid) res = max(res, query(ls, l, mid, x, y));
	if (y > mid) res = max(res, query(rs, mid + 1, r, x, y));
	return res;
}

signed main() 
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);    
	int C, T;
	cin >> C >> T;
	while (T--) 
	{
		int tot = 0;
		cin >> n >> m >> k >> d;
		for (rint i = 1; i <= m; i++) 
		{
			int x, y;
			cin >> x >> y >> w[i];
			l[i] = x - y + 1; 
			r[i] = x;
			b[++tot] = l[i] - 1;
			b[++tot] = r[i] + 1;
		}
		sort(b + 1, b + tot + 1);
		tot = unique(b + 1, b + tot + 1) - b - 1;
		for (rint i = 1; i <= m; i++) 
		{
			l[i] = lower_bound(b + 1, b + tot + 1, l[i] - 1) - b;
			r[i] = lower_bound(b + 1, b + tot + 1, r[i] + 1) - b;
			v[r[i]].push_back(i);
		}
		build(1, 1, tot);
		f[1] = 0;
		change(1, 1, tot, 1, 1, f[1] + b[1] * d);
		for (rint i = 2, j = 1; i <= tot; i++)
		{
			for (auto x : v[i]) change(1, 1, tot, 0, l[x], w[x]);
			while (b[i] - b[j] - 1 > k) j++;
			f[i] = max(f[i - 1], query(1, 1, tot, j, i - 1) - (b[i] - 1) * d);
			change(1, 1, tot, i, i, f[i] + b[i] * d);
		}
		cout << f[tot] << endl;
		for (rint i = 0; i <= tot; i++)
		{
			f[i] = 0;
			v[i].clear();
		}
	}
	return 0;
}

[NOIP2023] 双序列拓展 75pts solution

\(f_{i,j}\) 表示 \(A\) 序列的前 \(i\) 个是否能和 \(B\) 序列的前 \(j\) 个匹配,这个 dp 理论值无论怎么写也只有 35pts,但是考虑线段树优化,可以卡到 75pts。

struct Node
{
	int maxx;
	int minn;
}t[M];

#define ls p << 1
#define rs p << 1 | 1

void push_up(int p)
{
	t[p].maxx = max(t[ls].maxx, t[rs].maxx);
	t[p].minn = min(t[ls].minn, t[rs].minn);
}

void build(int p, int l, int r, int *a) 
{
	if (l == r)
	{
		t[p].maxx = t[p].minn = a[l];
		return;
	}
	int mid = (l + r) >> 1;
	build(ls, l, mid, a);
	build(rs, mid + 1, r, a);
	push_up(p);
}

int query_1(int p, int l, int r, int x, int v) 
{
	if (l == r) return (t[p].maxx < v ? l : 0);
	int mid = (l + r) >> 1;
	int res = 0;
	if (x > mid && t[rs].minn < v) res = query_1(rs, mid + 1, r, x, v);
	//加上一个 t[rs].minn < v 可以加速, 多 20 pts
	if (!res) res = query_1(ls, l, mid, x, v);
	return res;
}

int query_2(int p, int l, int r, int x, int v)
{
	if (l == r) return (t[p].minn >= v ? l : 0);
	int mid = (l + r) >> 1;
	int res = 0;
	if (x < mid && t[ls].maxx >= v)
		res = query_2(ls, l, mid, x, v);
	if (!res) res = query_2(rs, mid + 1, r, x, v);
	return res;
}

bool calc() 
{
	if (a[1] > b[1] && a[n] > b[m]) 
	{
		build(1, 1, m, b);
		int minn = t[1].minn;
		int j = 1;
		bool tag = 0;
		for (rint i = 1; i <= n; i++)
			if (a[i] <= minn)
				tag = 1;
		if (!tag)
		{
			for (rint i = 1; i <= n; i++) 
			{
				bool flag = 1;
				if (a[i] <= b[j]) flag = 0;
				else 
				{
					int res = query_2(1, 1, m, j, a[i]);
					if (!res) res = m;
					if (res == j) flag = 0;
					j = res;
				}
				if (!flag && a[i] <= b[j]) 
				{
					int res = query_1(1, 1, m, j, a[i]);
					if (!res) {	tag = 1; break;}
					j = res + 1;
				}
			}			
		}
		if (!tag && j == m) return 1;
	}
	if (b[1] > a[1] && b[m] > a[n]) 
	{
		build(1, 1, n, a);
		int minn = t[1].minn;
		int j = 1;
		bool tag = 0;
		for (rint i = 1; i <= m; i++)
			if (b[i] <= minn)
				tag = 1;
		if (!tag)
		{
			for (rint i = 1; i <= m; i++)
			{
				bool flag = 1;
				if (b[i] <= a[j]) flag = 0;
				else 
				{
					int res = query_2(1, 1, n, j, b[i]);
					if (!res) res = n;
					if (res == j) flag = 0;
					j = res;
				}
				if (!flag && b[i] <= a[j]) 
				{
					int res = query_1(1, 1, n, j, b[i]);
					if (!res) { tag = 1; break;}
					j = res + 1;
				}
			}			
		}
		if (!tag && j == n) return 1;
	}
	return 0;
}

signed main() 
{
	cin >> c >> n >> m >> q;
	for (rint i = 1; i <= n; i++) _a[i] = a[i] = read();
	for (rint i = 1; i <= m; i++) _b[i] = b[i] = read();
    cout << calc();
	while (q--) 
	{
		for (rint i = 1; i <= n; i++) a[i] = _a[i];
		for (rint i = 1; i <= m; i++) b[i] = _b[i];
		int kx = read(), ky = read();
		while (kx--) a[read()] = read();
		while (ky--) b[read()] = read();
	    cout << calc();
	}
	return 0;
}

Part2.扫描线

P5490 【模板】扫描线

实现思路:

拟扫描线从下向上扫过图形,计算出当前扫描线被截得的长度

扫描线每次会在碰到横边的时候停下来

可对图形面积产生影响的元素,这些横边左右端点的坐标

为了快速计算出截线段长度,可以将横边赋上不同的权值,对于一个矩形,其下边权值为 \(1\),上边权值为 \(-1\)

然后把所有的横边按照 \(y\) 坐标升序排序。这样,对于每个矩形,扫描线总是会先碰到下边,然后再碰到上边。那么就能保证扫描线所截的长度永远非负了。

这样操作以后,就可以和线段树扯上关系。先把所有端点在 \(x\) 轴上离散化

建立一棵线段树,其每个端点维护一条线段的信息: 被覆盖了多少次被多少个矩形所覆盖, 被整个图形所截的长度是多少

只要一条线段被覆盖,那么它肯定被图形所截。所以,整个问题就转化为了一个区间查询问题,即:每次将 当前扫描线扫到的边 对应的信息 按照之前赋上的权值更新,然后再查询线段树根节点的信息,最后得到当前扫描线扫过的面积。

#include <iostream>
#include <algorithm>

#define rint register int
#define int long long
#define endl '\n'

using namespace std;

const int N = 1e6 + 5;
const int M = N << 1;

int n, cnt = 0;
int x1, y1, x2, y2;
int b[M];

struct ScanLine 
{
	int l, r, h;
	int mark;
//  mark用于保存权值 (1 / -1)
	friend bool operator < (ScanLine a, ScanLine b)
	{
		return a.h < b.h;
	}
} line[M];

struct Node 
{
	int l, r;
	int sum;
	int len;
//  sum: 被完全覆盖的次数;
//  len: 区间内被截的长度。
} t[N << 2];

#define ls p << 1
#define rs p << 1 | 1

void build(int p, int l, int r) 
{
	t[p].l = l;
	t[p].r = r;
	t[p].len = 0;
	t[p].sum = 0;
	if (l == r) return;
	int mid = (l + r) >> 1;
	build(ls, l, mid);
	build(rs, mid + 1, r);
	return ;
}

void push_up(int p) 
{
	if (t[p].sum) t[p].len = b[t[p].r + 1] - b[t[p].l];
	else t[p].len = t[ls].len + t[rs].len;
}

void change(int p, int l, int r, int c) 
{
	if (b[t[p].r + 1] <= l || r <= b[t[p].l]) return;
	if (l <= b[t[p].l] && b[t[p].r + 1] <= r) 
	{
		t[p].sum += c;
		push_up(p);
		return;
	}
	change(ls, l, r, c);
	change(rs, l, r, c);
	push_up(p);
}

signed main() 
{
	cin >> n;
	for (rint i = 1; i <= n; i++) 
	{
		cin >> x1 >> y1 >> x2 >> y2;
		b[2 * i - 1] = x1;
		b[2 * i] = x2;
		line[2 * i - 1] = {x1, x2, y1, 1};
		line[2 * i] = {x1, x2, y2, -1};
	}
	n <<= 1;
	sort(line + 1, line + n + 1);
	sort(b + 1, b + n + 1);
	int tot = unique(b + 1, b + n + 1) - b - 1;
	build(1, 1, tot - 1);
	int ans = 0;
	for (rint i = 1; i < n; i++) 
	{
		change(1, line[i].l, line[i].r, line[i].mark);
		ans += t[1].len * (line[i + 1].h - line[i].h);
	}
	cout << ans << endl;
	return 0;
}

P1856 [IOI1998] 矩形周长

观察这三条扫描线扫过的纵边,有 纵边总长度\(=\sum2\times\)\(被截得的线段条数\)\(\times\)\(扫过的高度\)

横边总长度\(=\sum|\)\(上次截得的总长\)-\(现在截得的总长\)\(|\)

长并中的线段树还要维护线段的条数。另外,横边和纵边需分别计算。

#include <iostream>
#include <algorithm>

#define rint register int
#define int long long
#define endl '\n'

using namespace std;

const int N = 1e4 + 5;
const int M = N << 1;

int n, pre = 0;
int x1, y1, x2, y2;
int b[M];

struct ScanLine 
{
	int l, r, h;
	int mark;
//  mark用于保存权值 (1 / -1)
	friend bool operator < (ScanLine a, ScanLine b)
	{
		if (a.h == b.h) return a.mark > b.mark;
		return a.h < b.h;
	}
} line[M];

struct Node 
{
	int l, r;
	int sum;
	int len;
	int c;
	bool lc, rc;
//  sum: 被完全覆盖的次数;
//  len: 区间内被截的长度。
//  c表示区间线段条数
//  lc, rc分别表示左、右端点是否被覆盖
} t[N << 2];

#define ls p << 1
#define rs p << 1 | 1

void build(int p, int l, int r) 
{
	t[p].l = l;
	t[p].r = r;
	t[p].len = t[p].sum = 0;
	t[p].lc = t[p].rc = 0;
	t[p].c = 0;
	if (l == r) return;
	int mid = (l + r) >> 1;
	build(ls, l, mid);
	build(rs, mid + 1, r);
}

void push_up(int p) 
{
	if (t[p].sum) 
	{
		t[p].len = b[t[p].r + 1] - b[t[p].l];
		t[p].lc = t[p].rc = 1;
		t[p].c = 1;
		return ;
	} 
	t[p].len = t[ls].len + t[rs].len;
	t[p].lc = t[ls].lc, t[p].rc = t[rs].rc;
	t[p].c = t[ls].c + t[rs].c;
	if (t[ls].rc && t[rs].lc) t[p].c -= 1;
}

void change(int p, int l, int r, int c) 
{
	if (b[t[p].r + 1] <= l || r <= b[t[p].l]) return;
	if (l <= b[t[p].l] && b[t[p].r + 1] <= r) 
	{
		t[p].sum += c;
		push_up(p);
		return;
	}
	change(ls, l, r, c);
	change(rs, l, r, c);
	push_up(p);
}

ScanLine make_line(int l, int r, int h, int mark) 
{
	ScanLine res;
	res.l = l, res.r = r,
	res.h = h, res.mark = mark;
	return res;
}

signed main() 
{
	cin >> n;
	for (rint i = 1; i <= n; i++) 
	{
		cin >> x1 >> y1 >> x2 >> y2;
		b[2 * i - 1] = x1;
		b[2 * i] = x2;
		line[2 * i - 1] = {x1, x2, y1, 1};
		line[2 * i] = {x1, x2, y2, -1};
	}
	n <<= 1;
	sort(line + 1, line + n + 1);
	sort(b + 1, b + n + 1);
	int tot = unique(b + 1, b + n + 1) - b - 1;
	build(1, 1, tot - 1);
	int ans = 0;
	for (rint i = 1; i < n; i++) 
	{
		change(1, line[i].l, line[i].r, line[i].mark);
		ans += abs(pre - t[1].len);
		pre = t[1].len;
		ans += 2 * t[1].c * (line[i + 1].h - line[i].h);
	}
	ans += line[n].r - line[n].l;
	cout << ans << endl;
	return 0;
}

P9478 [NOI2023] 方格染色

分别算出所有直线和斜线的面积并。前者用扫描线,后者暴力求解即可。

此时我们还要减掉斜线和直线的交点数。我们可以对于每条斜线,枚举每一条直线,判断两者是否有交点。

由于某些交点可能会被扫描两次,用 map 维护交点是否被计算过。

#include <bits/stdc++.h>

#define rint register int
#define int long long
#define endl '\n'

#define mp make_pair

using namespace std;

const int N = 4e5 + 5;
const int Z = 4e6 + 5;
const int M = 1e3 + 5;

int c, n, m, q;
int tot1, tot2, tot3;
int cnt;
int a[N << 1], ans;
vector<int> v[N];
map<pair<int, int>, int> p;

struct ScanLine 
{
	int l, r, h;	
	int mark;
	friend bool operator < (ScanLine a, ScanLine b)
	{
		return a.h < b.h;
	}
} b[N << 1], line[M];
struct Node 
{
	int l, r;
	int sum;
	int len;
} t[Z];

#define ls p << 1
#define rs p << 1 | 1

void build(int p, int l, int r) 
{
	t[p].l = l;
	t[p].r = r;
	t[p].len = t[p].sum = 0;
	if (l == r) return;
	int mid = (l + r) >> 1;
	build(ls, l, mid);
	build(rs, mid + 1, r);
}

int find(int t) 
{
	int l = 1, r = tot2;
	while (l < r) 
	{
		int mid = (l + r) >> 1;
		if (a[mid] >= t) r = mid;
		else l = mid + 1;
	}
	return l;
}

void push_up(int p) 
{
	if (t[p].sum) t[p].len = a[t[p].r + 1] - a[t[p].l];
	else t[p].len = t[ls].len + t[rs].len;
}

void change(int p, int l, int r, int c) 
{
	if (t[p].l == l && t[p].r == r) 
	{
		t[p].sum += c;
		push_up(p);
		return ;
	}
	int mid = (t[p].l + t[p].r) >> 1;
	if (r <= mid) change(ls, l, r, c);
	else if (l > mid) change(rs, l, r, c);
	else change(ls, l, mid, c), change(rs, mid + 1, r, c);
	push_up(p);
}

signed main() 
{
	cin >> c >> n >> m >> q;
	while (q--)
	{
		int t;
		cin >> t;
		int x1, x2, y1, y2;
		cin >> x1 >> y1 >> x2 >> y2;
		if (x1 != x2 && y1 != y2) 
		{
			line[++cnt].l = x1;
			line[cnt].r = x2;
			line[cnt].h = y1;
			continue;
		}
		tot1++;
		b[tot1 * 2 - 1] = {x1, x2, y1, 1};
		b[tot1 * 2] = {x1, x2, y2, -1};
		a[tot1 * 2 - 1] = x1;
		a[tot1 * 2] = x2;
	}
	while (1) 
	{
		int CNT = 0;
		for (rint i = 1; i <= cnt; i++) 
		{
			for (rint j = 1; j <= cnt; j++) 
			{
				if (j != i && line[i].h && line[j].h && line[i].h - line[i].l == line[j].h - line[j].l) 
				{
					if (line[j].l >= line[i].l && line[j].r <= line[i].r || line[i].l <= line[j].l && line[j].l <= line[i].r && line[i].r <= line[j].r) 
					{
						CNT++;
						line[i].l = min(line[i].l, line[j].l);
						line[i].r = max(line[i].r, line[j].r);
						line[i].h = min(line[i].h, line[j].h);
						line[j].h = 0;
					}
				}
			}
		}
		if (!CNT) break;
	}
	for (rint i = 1; i <= cnt; i++) 
	{
		if (line[i].h) 
		{
			ans += line[i].r - line[i].l + 1;
		}
	}
	for (rint i = 1; i <= 2 * tot1; i += 2)
	{
		if (b[i].h == b[i + 1].h) 
		{
			if (!p[mp(b[i].h, 0)]) 
			{
				p[mp(b[i].h, 0)] = ++tot3;
			}
			v[p[mp(b[i].h, 0)]].push_back(i);
		} 
		else if (b[i].l == b[i].r)
		 {
			if (!p[mp(0, b[i].l)]) 
			{
				p[mp(0, b[i].l)] = ++tot3;
			}
			v[p[mp(0, b[i].l)]].push_back(i);
		}
	}
	for (rint i = 1; i <= cnt; i++) 
	{
		if (!line[i].h) continue;
		for (rint j = 1; j <= 2 * tot1; j += 2) 
		{
			int x = b[j].h - line[i].h + line[i].l;
			int y = b[j].l + line[i].h - line[i].l;
			if (b[j].l == b[j].r && b[j].l >= line[i].l && b[j].l <= line[i].r && y >= b[j].h && y <= b[j + 1].h) 
			{
				if (!p[mp(b[j].l, y)]) 
				{
					p[mp(b[j].l, y)] = 1;
					ans--;
				}
			}
			else if (b[j].h == b[j + 1].h && b[j].h >= line[i].h && b[j].h <= line[i].h + line[i].r - line[i].l && x >= b[j].l && x <= b[j].r) 
			{
				if (!p[mp(x, b[j].h)]) 
				{
					p[mp(x, b[j].h)] = 1; 
					ans--;
				}
			}
		}
	}
	for (rint i = 1; i <= 2 * tot1; i++) 
	{
		b[i].l--;
		if (i & 1) 
		{
			b[i].h--;
			a[i]--;
		}
	}
	sort(a + 1, a + 1 + 2 * tot1);
	sort(b + 1, b + 1 + 2 * tot1);
	tot2 = unique(a + 1, a + 1 + 2 * tot1) - a - 1;
	build(1, 1, tot2);
	for (rint i = 1; i < 2 * tot1; i++) 
	{
		int l = find(b[i].l);
		int r = find(b[i].r) - 1;
		change(1, l, r, b[i].mark);
		ans += t[1].len * (b[i + 1].h - b[i].h);
	}
	cout << ans << endl;
	return 0;
}
posted @ 2024-04-27 19:36  PassName  阅读(41)  评论(0编辑  收藏  举报