其他分享
首页 > 其他分享> > 2022.5.17 比赛题整理

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