题解:P17023 [ROI 2026 Day2] 夜,街道,路灯,药房

upd on 2026.7.11:修了一处笔误。

简单题。

题意:给出若干个路灯,每个路灯有位置 \(x_i\),照射范围 \(s_i\),即该路灯可以覆盖 \((x_i-s_i,x_i)\)\((x_i,x_i+s_i)\),你可以选择一个子集开灯,并让每一盏灯选择向左覆盖还是向右覆盖。定义一个子集时好的当且仅当存在一种分配方向的方式,使得被照亮的范围是连续线段且没有一段被覆盖多于一次,求有多少个非空集合是好的,对 \(10^9+7\) 取模。\(n\le 10^5\)。保证同一个位置的路灯最多只有两个。

做法:

观察部分分有大量部分为路灯位置完全不同,只有少量分是有相同,所以我们先做完全不同的再拓展一下应该就可以了。

朴素的想法是我直接把所有 \((x_i-s_i,x_i),(x_i,x_i+s_i)\) 丢进集合里,然后跑一个 dp 算出来这些区间刚好都搭在一起的方案数,记 \(dp_i\) 为在下标 \(i\) 结束的方案数即可,这个 dp 非常简单就不赘述了,但是这样会算重/错一些东西,我们来分析一下。

首先我们注意到我们不会区分同一个路灯的两个区间,也就是我有可能连续用两个同个路灯的区间,这个比较简单,我们在 \((x_i,x_i+s_i)\) 这个区间转移的时候减掉一个 \(dp_{x_i-s_i}\) 即可。

然后还有问题,因为题目要求的是集合数量而不是分配方式数量,所以如果一个灯的集合存在多种照亮方式会错掉。但是经过简单的讨论,每个灯向左向右发现只有可能是所有灯的 \(s\) 相等且刚好两两间距 \(s\),并且刚好会算多一次。那么我们把 \(s_i = s\) 的灯拉出来,算一下只用 \((x_i-s_i,x_i)\) 这样的区间会有多少种方案减掉即可。

然后考虑怎么做有相同的情况,我们继续考虑什么情况会算多,发现应该是在同一个位置的路灯,第一个向左第二个向右和第一个向右第二个向左的情况会爆炸,但是发现除了这两个路灯的 \(s\) 都相等的情况,其他的应该只在其他路灯的 \(s_i\) 都为 \(s_1-s_2\) 时存在问题,这里假设 \(s_1>s_2\),对于 \(s\) 不相等情况,我们直接把 \((x_i-s_1,x_i+s_2)\) 这个区间丢进去计算就可以,相等的情况,我们发现直接在计算总区间个数的时候把这个区间丢进去,并且记一个 \(-1\) 的代价即可,这样我们的 dp 计算就是完全对的。

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 5e5 + 5, N = 5e5, mod = 1e9 + 7, inf = 2e9;
struct node {
	int l, r, lt, val;
} ;
vector<node> vec[maxn];
int n;
int ls[maxn * 2], tot, dp[maxn];
vector<pair<int, int> > trans[maxn * 2];
vector<int> coef[maxn];
int solve(vector<node> &vec) {
	tot = 0;
	for (int i = 0; i < vec.size(); i++)
		ls[++tot] = vec[i].l, ls[++tot] = vec[i].r;
	sort(ls + 1, ls + tot + 1);
	tot = unique(ls + 1, ls + tot + 1) - ls - 1;
	for (int i = 1; i <= tot; i++)
		trans[i].clear(), coef[i].clear();
	for (int i = 0; i < vec.size(); i++) {
		vec[i].l = lower_bound(ls + 1, ls + tot + 1, vec[i].l) - ls,
		vec[i].r = lower_bound(ls + 1, ls + tot + 1, vec[i].r) - ls,
		vec[i].lt = (vec[i].lt <= -inf ? vec[i].lt : lower_bound(ls + 1, ls + tot + 1, vec[i].lt) - ls);
		trans[vec[i].l].push_back(make_pair(vec[i].r, vec[i].lt));
		coef[vec[i].l].push_back(vec[i].val);
//		cout << vec[i].val << endl;
	}
	int ans = 0;
	for (int i = 1; i <= tot; i++)
		dp[i] = 0;
	for (int i = 1; i <= tot; i++) {
		ans = (ans + dp[i]) % mod;
//		cout << i << " " << ls[i] << " " << dp[i] << endl;
		dp[i] = (dp[i] + 1) % mod;
		for (int j = 0; j < trans[i].size(); j++) {
//			if((dp[i] - (trans[i][j].second == -inf ? 0 : dp[trans[i][j].second])) * coef[i][j] < 0)
//				cout << i << " " << trans[i][j].first << endl;
			dp[trans[i][j].first] = (dp[trans[i][j].first] + (dp[i] - (trans[i][j].second == -inf ? 0 : dp[trans[i][j].second])) * coef[i][j] % mod + mod) % mod;
		}
	}
//	cout << endl;
	return ans;
}
map<int, int> mp;
signed main() {
	cin >> n;
	for (int i = 1; i <= n; i++) {
		int x, s; cin >> x >> s;
		vec[s].push_back(node{x - s, x, -inf, 1});
		vec[0].push_back(node{x - s, x, -inf, 1});
		vec[0].push_back(node{x, x + s, x - s, 1});
		if(mp.find(x) != mp.end()) {
			int t = mp[x];
			if(s < t)
				swap(s, t);
			if(s != t)
				vec[s - t].push_back(node{x - s, x + t, -inf, 1});
			else
				vec[s - t].push_back(node{x - s, x + t, -inf, mod - 1});
		}
		else
			mp[x] = s;
	}
	int ans = solve(vec[0]);
	for (int i = 1; i <= N; i++)
		if(vec[i].size())
			ans = (ans - solve(vec[i]) + mod) % mod;
	cout << ans << endl;
	return 0;
}
posted @ 2026-07-03 15:08  LUlululu1616  阅读(30)  评论(0)    收藏  举报