省选联考 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\),判定这个箱子会不会被牵连到。
不会被牵连到,当且仅当:
这里 \(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");
}

浙公网安备 33010602011771号