省选联考 2025 补题记录

目前只配补 T1。

D1T1 幸运数字

我们考虑判断每个数字 \(v\) 是否可能是中位数。这里有一个充要条件:

  • 记小于 \(v\) 的数有 \(x\) 个,等于 \(v\) 的数有 \(y\) 个,大于 \(v\) 的数有 \(z\) 个。那么 \(v\) 是中位数等价于 \(x < y+z\)\(z \le x+y\)\(y>0\)

然后注意到如果 \(v\) 被多个 \([l_{i,2}, r_{i,2}]\) 覆盖,那么我们可以让这些 \(b_i\) 都等于 \(v\),且 \(a_i\) 都取 \(r_{i,1}\)。直观的感受是,等于 \(v\) 的数量 \(y\) 越多肯定是不劣的。

剩下的我们就想找出一组 \(x,z\) 看看是否满足条件。记录一个 \(premx_i\) 表示 \(r_{k,2} \le i\)\(r_{k,1}\) 之和,\(premn_i\) 表示 \(l_{k,1}\) 之和。

不难发现,对于数字 \(v\),小于 \(v\) 的个数 \(x\) 的取值范围恰好就是 \([premn_{v-1}, premx_{v-1}]\)
同理可以记录,\(sufmx\)\(sufmn\)。于是 \(z\) 的取值范围就是 \([sufm n_{v+1}, sufmx_{v+1}]\)

怎么判定是否符合条件呢?
首先,如果 \(x=z\),那么 \(v\) 一定可以是中位数,对应的就是 pre 和 suf 两个区间有交。
此外,大概的感觉是,希望 \(|x-z|\) 越小越好。用反证法不难证。
只需要带入端点值,即 \((x,z)=(premx, sufmn)\)\((x,z)=(premn,sufmx)\) 就可以了。\(O(1)\) 轻松判断。

最后看看如何不去枚举值域。
发现以所有的 \(l_{i,2},r_{i,2}\) 为端点可以获得 \(O(n)\) 条线段。然后每个分割出来的线段上,任意两点的可行性都是等价的。用上面的方法,判一下这个线段就可以了。

因为要排序和离散化,故总时间复杂度 \(O(n \log n)\)

#define inf 0x3f3f3f3f
#define Linf 0x3f3f3f3f3f3f3f3f
#define pii pair<int, int> 
#define ull unsigned long long
#define all(v) v.begin(), v.end()
#define upw(i, a, b) for(int i = (a); i <= (b); ++i)
#define dnw(i, a, b) for(int i = (a); i >= (b); --i)

template<class T> bool vmax(T &a, T b) { return b > a ? a = b, true : false; }
template<class T> bool vmin(T &a, T b) { return b < a ? a = b, true : false; }
template<class T> void clear(T &x) { T().swap(x); }

const int N = 2e5+2;

#define int long long

int n, l1[N], r1[N], l2[N], r2[N];
int pos[N];

int premn[N << 1], premx[N << 1];
int sufmn[N << 1], sufmx[N << 1];

int check(int x, int z, int y) { return x < y + z && z <= y + x; }

void WaterM() {
	cin >> n;
	upw(i, 1, n) scanf("%lld%lld%lld%lld", &l1[i], &r1[i], &l2[i], &r2[i]), ++r2[i];
	
	vector<int> va{-1};
	upw(i, 1, n) va.push_back(l2[i]), va.push_back(r2[i]);
	sort(all(va)), va.erase(unique(all(va)), va.end());
	vector<int> mark(va.size());
	upw(i, 1, n) {
		int x = lower_bound(all(va), l2[i]) - va.begin();
		int y = lower_bound(all(va), r2[i]) - va.begin();
		mark[x] += r1[i], mark[y] -= r1[i];
	}
	// return;
	
	// vector<pii> vec;
	// for(auto [x, y] : mark) vec.emplace_back(x, y);
	vector<tuple<int, int, int> > seg;
	upw(i, 2, (int)mark.size() - 1)
		seg.emplace_back(va[i-1], va[i], mark[i-1]);
	
	vector<int> vl{-1}, vr{-1};
	upw(i, 1, n) vl.push_back(l2[i]), vr.push_back(r2[i]);
	sort(all(vl)), vl.erase(unique(all(vl)), vl.end());
	sort(all(vr)), vr.erase(unique(all(vr)), vr.end());
	upw(i, 1, n) {
		l2[i] = lower_bound(all(vl), l2[i]) - vl.begin();
		r2[i] = lower_bound(all(vr), r2[i]) - vr.begin();
	}
	
	upw(i, 1, n << 1) premn[i] = premx[i] = sufmn[i] = sufmx[i] = 0;
	upw(i, 1, n) pos[i] = i;
	sort(pos+1, pos+n+1, [&] (int i, int j) { return r2[i] < r2[j]; });
	upw(k, 1, n) {
		int p = pos[k], i = r2[pos[k]], j = r2[pos[k-1]];
		premn[i] = premn[j] + l1[p];
		premx[i] = premx[j] + r1[p];
	}
	sort(pos+1, pos+n+1, [&] (int i, int j) { return l2[i] > l2[j]; });
	upw(k, 1, n) {
		int p = pos[k], i = l2[pos[k]], j = l2[pos[k-1]];
		sufmn[i] = sufmn[j] + l1[p];
		sufmx[i] = sufmx[j] + r1[p];
	}
	
	int ans = 0, sum = 0;
	for(auto [l, r, val] : seg) {
		sum += val;
		if(sum == 0) continue;
		int len = r - l;
		l = upper_bound(all(vr), l) - vr.begin() - 1;
		r = lower_bound(all(vl), r) - vl.begin();
		if(premn[l] <= sufmn[r] && sufmn[r] <= premx[l]
		|| premn[l] <= sufmx[r] && sufmx[r] <= premx[l]
		|| sufmn[r] <= premn[l] && premn[l] <= sufmx[r]
		|| sufmn[r] <= premx[l] && premx[l] <= sufmx[r]
		|| check(premx[l], sufmn[r], sum) 
		|| check(premn[l], sufmx[r], sum)
		) ans += len;
	}
	cout << ans << '\n';
}

D2T1 推箱子

有一个观察是,不管推箱子的顺序如何改变,每一个箱子最终走过的路程都是相同的(除非闲着,刻意来回推),也就是,不会使某个箱子“变劣”。
很容易想到先推时间限制较早的。对于两个箱子 \(i,j,t_i < t_j\),不管两个箱子会不会相互“挤压”,先推 \(j\) 都不比先推 \(i\) 好。

然后就是一个模拟过程。对时间排序,维护每个箱子的位置,每次看看把箱子 \(i\) 归位需要花多少时间,加进总贡献。然后判断就是,看看推完 \(i\) 之后总贡献和 \(t_i\) 的大小。

这里需要一个数据结构维护箱子的位置。我们看看需要哪些操作。
对于把某一个箱子归位的操作,可能牵连到多个箱子。由 a_i,b_i$ 的单调性,显然这些箱子会构成一段区间。
具体地,可能会有多个箱子成公差为 \(1\) 的等差数列,要想将 \(i\) 挪一个格子就要将第 \(i+ 1\) 个箱子挪动一格,要想将 \(i+1\) 挪动一格就要将第 \(i+2\) 个箱子挪动一格……
显然推完一个箱子,这些被牵连到的箱子会在 \(b_i\) 之后排成一队公差等于 \(1\) 的等差数列。(不急着往后挪)

\(a_i >b_i\) 的情况同理。

  • 快速计算一个箱子归位的时间贡献,可以使用区间求和,看看牵连到的 \(k\) 个箱子的初态位置之和,和末态位置之和,二者一减就是时间贡献。(正确性的保证来源于,这牵连的 \(k\) 个箱子,都只会朝一个方向动)
  • 要把一段区间中 \(k\) 个箱子赋值成一段等差数列,快速从把一段箱子初态变成末态。
  • 快速找到被牵连的箱子的区间。这里可以二分实现。

这个数据结构就是线段树。

下面说说如何找到被牵连的箱子区间。
对于箱子 \(i\),我们二分到一个 \(mid \ge i\),判定这个箱子会不会被牵连到。
不会被牵连到,当且仅当:

\[b_i + (mid-i+1) - 1 < p_{mid+1} \]

这里 \(p_i\) 表示第 \(i\) 个箱子当前的位置。

变形成 \(b_i-i-1 < p_{mid+1} - (mid+1)\)
也就是我们要找,第一个大于 \(b_i-i-1\)\(p_j-j\)。维护这个的最大值,可以做到在线段树上二分,单次时间复杂度 \(O(\log n)\)

对于 \(a_i > b_i\) 的情况,类似地要找到第一个小于 \(b_i-i+1\)\(p_j-j\)。维护这个的最小值就行。

然后显然每次推完箱子,都有 \(p_i + 1\le p_{i+1}\),故 \(p_i-i \le p_{i+1}-(i+1)\)。于是二分的单调性是有保证的。

值得一提的是,修改操作(区间赋值公差为 \(1\) 的等差数列)会让 \(p_i-i\) 都相等。于是就变得比较好写。

故总时间复杂度 \(O(n\log n)\)。虽然线段树维护很多信息常数大,但是开 2s 无所畏惧。

#define inf 0x3f3f3f3f
#define Linf 0x3f3f3f3f3f3f3f3f
#define pii pair<int, int> 
#define ull unsigned long long
#define all(v) v.begin(), v.end()
#define upw(i, a, b) for(int i = (a); i <= (b); ++i)
#define dnw(i, a, b) for(int i = (a); i >= (b); --i)

template<class T> bool vmax(T &a, T b) { return b > a ? a = b, true : false; }
template<class T> bool vmin(T &a, T b) { return b < a ? a = b, true : false; }
template<class T> void clear(T &x) { T().swap(x); }

const int N = 2e5+10;

#define int long long

namespace SegmentTree {
	struct node {
		node *l, *r;
		int sum, tag, len;
		int mx, tagmx;
		int mn, tagmn;
		void up() { sum = l->sum + r->sum, mx = max(l->mx, r->mx), mn = min(l->mn, r->mn); }
		void upd(int v, int t) { sum = (2*v + len - 1) * len / 2, tag = v, mx = tagmx = mn = tagmn = t; }
		void down() { if(tag) l->upd(tag, tagmx), r->upd(tag + l->len, tagmx), tag = 0; }
	} pool[N << 1], *tmp = pool;
	
	node *newnode() {
		tmp->l = tmp->r = NULL;
		tmp->sum = tmp->tag = tmp->len = 0;
		tmp->mx = tmp->tagmx = 0;
		tmp->mn = tmp->tagmn = 0;
		return tmp++;
	}
	
	struct Sgt {
		node *root;
		int l, r;
		void build(node *&p, int l, int r, int *a) {
			p = newnode();
			p->len = r - l + 1;
			if(l == r) return p->sum = a[l], p->mx = p->mn = a[l] - l, void();
			int mid = l + r >> 1;
			build(p->l, l, mid, a), build(p->r, mid+1, r, a);
			p->up();
		}
		void build(int l, int r, int *a) {
			tmp= pool;
			this->l = l, this->r = r;
			build(root, l, r, a);
		}
		void update(node *p, int l, int r, int ql, int qr, int v) {
			if(l == ql && r == qr) return p->upd(v, v - l);
			int mid = l + r >> 1;
			p->down();
			if(mid >= qr) update(p->l, l, mid, ql, qr, v);
			else if(mid < ql) update(p->r, mid+1, r, ql, qr, v);
			else {
				update(p->l, l, mid, ql, mid, v);
				update(p->r, mid+1, r, mid+1, qr, v + (mid - ql + 1));
			}
			p->up();
		}
		void update(int ql, int qr, int v) { update(root, l, r, ql, qr, v); }
		
		int qsum(node *p, int l, int r, int ql, int qr) {
			if(l == ql && r == qr) return p->sum;
			int mid = l + r >> 1;
			p->down();
			if(mid >= qr) return qsum(p->l, l, mid, ql, qr);
			if(mid < ql) return qsum(p->r, mid+1, r, ql, qr);
			return qsum(p->l, l, mid, ql, mid) + qsum(p->r, mid+1, r, mid+1, qr);
		}
		int qsum(int ql, int qr) { return qsum(root, l, r, ql, qr); }
		
		int qmin(node *p, int l, int r, int ql, int qr) {
			if(l == ql && r == qr) return p->mn;
			int mid = l + r >> 1;
			p->down();
			if(mid >= qr) return qmin(p->l, l, mid, ql, qr);
			if(mid < ql) return qmin(p->r, mid+1, r, ql, qr);
			return min(qmin(p->l, l, mid, ql, mid), qmin(p->r, mid+1, r, mid+1, qr));
		}
		int qmin(int ql, int qr) { return qmin(root, l, r, ql, qr); }
		
		int qposback(node *p, int l, int r, int k) {	//向后查找第一个大于k的位置
			if(l == r) return l;
			int mid = l + r >> 1;
			p->down();
			if(p->l->mx <= k) return qposback(p->r, mid+1, r, k);
			return qposback(p->l, l, mid, k);
		}
		int qposback(int k) { return qposback(root, l, r, k); }
		
		int qposfront(node *p, int l, int r, int k) {	//向前查找第一个小于k的位置
			if(l == r) return l;
			int mid = l + r >> 1;
			p->down();
			if(p->r->mn < k) return qposfront(p->r, mid+1, r, k);
			return qposfront(p->l, l, mid, k);
		}
		int qposfront(int k) { return qposfront(root, l, r, k); }
	};
}
using SegmentTree::Sgt;

int n, a[N], b[N], t[N];
int pos[N];

Sgt tr;

void WaterM() {
	cin >> n, ++n;
	upw(i, 2, n) scanf("%lld%lld%lld", &a[i], &b[i], &t[i]);
	a[1] = b[1] = -Linf, t[1] = Linf;
	++n, a[n] = b[n] = t[n] = Linf;
	
	tr.build(1, n, a);
	upw(i, 1, n) pos[i] = i;
	sort(pos+2, pos+n, [&] (int i, int j) { return t[i] < t[j]; });
	
	int Time = 0;
	upw(k, 2, n-1) {
		int i = pos[k];
		if(a[i] < b[i]) {
			int p = tr.qposback(b[i] - i - 1) - 1;
			vmax(p, i);
			Time -= tr.qsum(i, p);
			tr.update(i, p, b[i]);
			Time += tr.qsum(i, p);
		}
		else {
			int p = tr.qposfront(b[i] - i + 1) + 1;
			vmin(p, i);
			Time += tr.qsum(p, i);
			tr.update(p, i, b[i] - (i-p));
			Time -= tr.qsum(p, i);
		}
		if(Time > t[i]) return puts("No"), void();
	}
	puts("Yes");
}
posted @ 2025-08-28 12:08  Water_M  阅读(12)  评论(0)    收藏  举报