2022.5.17 比赛题整理
作者:互联网
2022.5.17 2022初一测试六
总结
T1:二分 + 贪心。
T2:模拟(对某某数之积之和的简化与运算)。
\({\color{Red}{\text{[主席树 好题] }}}\)T3:二分求区间中位数 + 主席树维护。
T4:?(目前还未改。)
Problem A
“打破定式思维”,题面不含“最小(大)值最大(小)”时也有可能是用二分做。
二分所用秒数,然后贪心验证是否可行即可。
#include<bits/stdc++.h>
using namespace std;
#define rep(i, a, b) for(register int i = a; i <= b; ++i)
const int maxn = 1e5 + 5;
int n;
double k, a[maxn];
double l, r, mid;
inline bool chck(double t){
double x, s, y;
s = a[1] + t;
rep(i, 2, n){
x = a[i] - t, y = a[i] + t;
if(s + k < x) return 0;
if(s + k > y) s = y;
else s += k;
}
return 1;
}
int main(){
scanf("%lf", &k);
scanf("%d", &n);
rep(i, 1, n) scanf("%lf", &a[i]);
l = 0.0, r = 99999999.0;
while(r - l > 0.0001){
mid = (l + r) / 2.0;
if(chck(mid)) r = mid;
else l = mid;
}
printf("%.3f", r);
return 0;
}
Problem B
\((x + z)* (Num_x +Num_z) = x* (Num_x + Num_z) + z* (Num_x + Num_z)\)
而对于题目中对 \(x\) 和 \(z\) 的限制我们可以简化为:下标同奇偶,所染颜色相同。(这大概是此题唯一难点。)
所以我们就可以在输入的同时处理出:下标奇偶相同、颜色相同的位置所代表的数之和以及这些位置的数量分别是多少。
这样我们就可以遍历 1~n,对于第 \(i\) 个位置,计算所有 \(i* (Num_i + Num_z)\) 的总和。
注意取模,要点见注释。
#include<bits/stdc++.h>
using namespace std;
#define rep(i, a, b) for(register int i = a; i <= b; ++i)
const int maxn = 1e5 + 5;
const int mod = 10007;
int n, m;
int a[maxn], c[maxn];
int num[maxn][2], tot[maxn][2];
int ans;
int main(){
scanf("%d%d", &n, &m);
rep(i, 1, n) scanf("%d", &a[i]), a[i] %= mod;
rep(i, 1, n){
scanf("%d", &c[i]);
num[c[i]][i % 2] += a[i];
tot[c[i]][i % 2] += 1;
}
rep(i, 1, n){
ans += (i % mod * ((num[c[i]][i % 2] +
(tot[c[i]][i % 2] - 2)
\*减去的2种:一个是前面 num 数组中统计过一次 a[i] 了,另一个是减去 i 和自己搭配的情况*\
% mod * a[i] + mod) % mod)) % mod;
}
printf("%d\n", ans % mod);
return 0;
}
Problem C
非常非常好的一道主席树的题目。(写了三天才 a
1 求中位数
拿到题目首先肯定会去思考怎么求区间中位数。
按照以往求中位数的方法——对顶堆,显然不行,时间肯定会炸。
那就要引入一个新的求中位数的方法了:二分中位数大小,然后将大于等于该数的数的值设为 1,否则设为 -1,然后求区间的和,若大于等于 0,则意味着真正的中位数一定大于等于该数,否则小于该数,时间复杂度同对顶堆一样,\(O(nlogn)\)。
2 线段树维护
这时候我们考虑到题目的另一个限制:左端点在 \([a,b]\) 间,右端点在 \([c,d]\) 间。
唯一能够直接确定的是区间和一定包含 \([b+1,c-1]\) 的和。
那么为了让中位数最大,我们就要尽量让区间和最大,所以对于区间 \([a,b]\),我们取和最大的后缀,同理,区间 \([c,d]\) 取和最大的前缀。
确定了如何选取区间内的数,我们考虑如何快速求出它们。
区间求和...不难想到线段树。
再去优化一下,对初始数组进行离散化,那么二分出的中位数的范围就在 1~n 之间。
所以对于每个二分出的 \(mid\),我们构造一颗线段树,用来求上述的区间和/区间最大前后缀。
每次 \(check(mid)\) 的复杂度就变成了 \(O(logn)\)。
3 空间优化——主席树维护
这样一来,空间会炸。
那么同时在多棵线段树上进行空间优化...不难想到主席树。考虑怎样转化为主席树。
我们试着去找离散化后的数组 \(num[]\) 每个位置之间的相同与联系。
我们发现,假如我们已经对当 \(mid=num[i]\) 这种情况按照 2 中所说建立了一颗线段树(比它大(等于)的点权值设为 1,否则设为 -1),那么此时要想再建立一颗 \(mid = num[i+1]\) 这种情况的线段树(\(i\) 和 \(i+1\) 均在离散化后的数组 \(num[]\) 中,即满足单调递增),我们只需要将值为 \(num[i]\) 的点在线段树中的权值改为 -1 即可,其余的值不变。
有了上述结论,我们就可以将线段树转化为主席树去优化了。
更详细地,对于 \(rt_i\),以它为根节点的线段树内部,值小于 \(num_i\) 的点权值为 -1,否则为 1。
那么 \(rt_1\) 内部的点的点权没有值为 -1 的情况。
4 总结/思考
重新回顾这道题,发现看到题面首先容易把注意力放在“区间不定”上,但是实际上突破口在于如何求解中位数。在确定怎么求解中位数之后,将“区间不定”转化为“区间确定”也就迎刃而解了。
我们还会发现,随着思路的不断深入,我们先是优化时间复杂度,再是逐渐去优化空间复杂度,但不论优化哪个,都不会是一蹴而就的。
最后,此解法时间复杂度 \(O(nlogn^2)\),空间复杂度 \(O(nlogn)\)。
5 代码
还有一点处理主席树部分的细节需要见代码注释。
#include<bits/stdc++.h>
using namespace std;
#define rep(i, a, b) for(register int i = a; i <= b; ++i)
#define ls t[x].l
#define rs t[x].r
#define s(x) t[x].sum
#define lx(x) t[x].lmx
#define rx(x) t[x].rmx
const int maxn = 2e4 + 5;
int n, m, lst;
int x1, x2, x3, x4;
int ux[15];
int a[maxn], num[maxn], tot;
vector <int> pos[maxn];
int rt[maxn];
struct tree{
int l, r;
int sum, lmx, rmx;
bool el, er;//若左儿子指向的是以前建的节点那么 el=0,否则即新建立了一个左儿子,那么 el=1
}t[800005];
int cnt, ans;
inline int plc(int x){
return lower_bound(num + 1, num + tot + 1, x) - num;
}
inline void up(int x){
s(x) = s(ls) + s(rs);
lx(x) = max(lx(ls), s(ls) + lx(rs));
rx(x) = max(rx(rs), s(rs) + rx(ls));
}
inline int build(int l, int r){
int x = ++cnt;
if(l == r){
if(plc(a[l]) <= 1) //是否真的需要这个判断???
s(x) = lx(x) = rx(x) = 1;
else
s(x) = lx(x) = rx(x) = 1;
return x;
}
int mid = l + r >> 1;
t[x].el = t[x].er = 1;
ls = build(l, mid), rs = build(mid + 1, r);
up(x);
return x;
}
inline int update(int x, int y, int l, int r, int k, int c, bool f){
if(!f) x = ++cnt;
if(l == r){
s(x) = lx(x) = rx(x) = c;
return x;
}
int mid = l + r >> 1;
if(k <= mid){
if(!t[x].er) rs = t[y].r;
if(!t[x].el)
t[x].el = 1,
ls = update(x, t[y].l, l, mid, k, c, 0);
//如果要修改的节点在左子树,我们要新建一个左子树,而不是直接修改覆盖以前的信息
else
ls = update(ls, t[y].l, l, mid, k, c, 1);
}
else{
if(!t[x].el) ls = t[y].l;
if(!t[x].er)
t[x].er = 1,
rs = update(x, t[y].r, mid + 1, r, k, c, 0);
//右子树同理
else
rs = update(rs, t[y].r, mid + 1, r, k, c, 1);
}
up(x);
return x;
}
inline int qy(int x, int l, int r, int L, int R){
if(l >= L and r <= R) return s(x);
int mid = l + r >> 1, sm = 0;
if(L <= mid) sm += qy(ls, l, mid, L, R);
if(R > mid) sm += qy(rs, mid + 1, r, L, R);
return sm;
}
inline tree ql(int x, int l, int r, int L, int R){
if(l >= L and r <= R) return t[x];
int mid = l + r >> 1;
if(L <= mid and mid < R){
tree ret, lt, Rt;
lt = ql(ls, l, mid, L, R);
Rt = ql(rs, mid + 1, r, L, R);
ret.sum = lt.sum + Rt.sum;
ret.lmx = max(lt.lmx, lt.sum + Rt.lmx);
return ret;
}
else if(L <= mid)
return ql(ls, l, mid, L, R);
else
return ql(rs, mid + 1, r, L, R);
}
inline tree qr(int x, int l, int r, int L, int R){
if(l >= L and r <= R) return t[x];
int mid = l + r >> 1;
if(L <= mid and mid < R){
tree ret, lt, Rt;
lt = qr(ls, l, mid, L, R);
Rt = qr(rs, mid + 1, r, L, R);
ret.sum = lt.sum + Rt.sum;
ret.rmx = max(Rt.rmx, Rt.sum + lt.rmx);
return ret;
}
else if(L <= mid)
return qr(ls, l, mid, L, R);
else
return qr(rs, mid + 1, r, L, R);
}
inline bool chck(int md){
int res = 0;
if(x2 + 1 <= x3 - 1)
res += qy(rt[md], 1, n, x2 + 1, x3 - 1);
res += qr(rt[md], 1, n, x1, x2).rmx;
res += ql(rt[md], 1, n, x3, x4).lmx;
return res >= 0;
}
int main(){
scanf("%d", &n);
rep(i, 1, n)
scanf("%d", &a[i]), num[++tot] = a[i];
sort(num + 1, num + tot + 1);
tot = unique(num + 1, num + tot + 1) - num - 1;
rep(i, 1, n)
pos[plc(a[i])].push_back(i);
rt[1] = build(1, n);
rep(i, 2, tot) rep(j, 0, (pos[i - 1].size() - 1))
rt[i] = update(rt[i], rt[i - 1], 1, n, pos[i - 1][j], -1, rt[i] > 0);
scanf("%d", &m);
while(m--){
scanf("%d%d%d%d", &ux[0], &ux[1], &ux[2], &ux[3]);
rep(i, 0, 3) ux[i] += lst, ux[i] %= n;
sort(ux, ux + 4);
x1 = ux[0] + 1, x2 = ux[1] + 1, x3 = ux[2] + 1, x4 = ux[3] + 1;
int l = 1, r = tot;
while(l <= r){
int mid = l + r >> 1;
if(chck(mid)) ans = mid, l = mid + 1;
else r = mid - 1;
}
printf("%d\n", lst = num[ans]);
}
return 0;
}
Problem D
——\(End\)——
标签:比赛,17,int,rep,mid,中位数,num,2022.5,ux 来源: https://www.cnblogs.com/gsn531/p/16496489.html