2022.07.16模拟赛总结


暑假集训模拟赛Day1

总述

真想不到,我们普转提竟与提高组同一套题目!!!
本场比赛:

  • 满分 500 p t s 500pts 500pts
  • 得分 325 p t s 325pts 325pts
  • R a n k Rank Rank 5 5 5
  • R a t i n g Rating Rating + 86 +86 +86

能打成这样已经超出我的意料了,这个成绩也是有很大水分的。肯定不是因为数据太水了……

题目详情

看来 P D F PDF PDF类型的题目是传不进 c s d n csdn csdn里了……

T1 取餐号

手动粘题……
这道题无需多言,简单的埃氏筛即可 A C AC AC但是我为什么要花费大量精力去记一个不是最优的算法呢??

//欧拉筛代码
void Prime() {
	for(int i = 2; i <= n; i ++) {
		if(!v[i]) {
			v[i] = i;
			prime[++ len] = i;
		}
		for(int j = 1; j <= len; j ++) {
			if(prime[j] > v[i] || prime[j] > n / i) break;
			v[i * prime[j]] = prime[j];
		}
	}
}

喜提 100 p t s 100pts 100pts

T2 堆人塔

手动粘题2.0
在这里插入图片描述

这道题题意很容易就能搞明白:每次凭借区间中最大值将区间分为两个小区间,再分别不断取最大值、划分小区间……
显然是一道经典的递归题目。但是如果暴力去求每个区间的最大值显然是不行的,所以我们需要使用 S T ST ST表或线段树进行优化。因为本蒟蒻不会线段树,在此给出ST表代码

void Get_ST() {
	for(int i = 1; i <= n; i ++)
		t[0][i].num = a[i], t[0][i].locat = i;
	int T = log2(n) + 1;
	for(int i = 1; i <= T; i ++)
		for(int j = 1; j <= n - (1 << i) + 1; j ++) {
			my_str2 x, y;
			x = t[i - 1][j], y = t[i - 1][j + (1 << (i - 1))];
			if(x.num < y.num) t[i][j] = y;
			else t[i][j] = x;
		}
}

int Find(int x, int y) {
	int len = y - x + 1;
	len = log2(len);
	if(t[len][x].num > t[len][y - (1 << len) + 1].num) return t[len][x].locat;
	else return t[len][y - (1 << len) + 1].locat;
}

void dfs_1(int dep, int l, int r) {
	if(l > r) return ;
	int loc = Find(l, r);
	b[loc] = dep;
	if(loc == l) dfs_1(dep + 1, l + 1, r);
	else if(loc == r) dfs_1(dep + 1, l, r - 1);
	else {
		dfs_1(dep + 1, l, loc - 1);
		dfs_1(dep + 1, loc + 1, r);
	}
	return ;
}

喜提 100 p t s 100pts 100pts

题外话: 某个神仙同学 c z l czl czl在没学笛卡尔树的情况下,考场上用单调栈切掉本题,而他的代码正是笛卡尔树的代码。

//czl神的代码(注释均为czl本人注释)
#include<bits/stdc++.h>//貌似要做单调栈?   每个数都是它左边或右边第一个大于它的儿子(取小的) 
using namespace std;
const int N = 1e5 + 10;
int n, a[N], cnt[N], L[N], R[N], head[N], tot, st;
stack< int > s;
struct edge{
	int v, last;
}E[N * 2];
void Get(){
	for(int i = 1; i <= n; i++){ 
		L[i] = R[i] = 1e6;
	} 
	for(int i = 1; i <= n; i++){//找每个数的两边第一个大于它的数 
		while(!s.empty() && a[i] > a[s.top()]){
			int t = s.top();
			R[t] = i;//右边第一个比它大的
			s.pop(); 
		}
		if(!s.empty()){
			int t = s.top();
			L[i] = t;//左边第一个比他大的 
		}
		s.push(i);
	}
}
void add(int u, int v){
	E[++tot].v = v;
	E[tot].last = head[u];
	head[u] = tot;
}
void dfs(int s){
	for(int i = head[s]; i != 0; i = E[i].last){
		int v = E[i].v;
		cnt[v] = cnt[s] + 1;
		dfs(v);
	}
}
int main(){
	freopen("tower.in", "r", stdin);
	freopen("tower.out", "w", stdout);
	scanf("%d", &n);
	for(int i = 1; i <= n; i++)
		scanf("%d", &a[i]);
    Get(); 
    for(int i = 1; i <= n; i++){//建图 
    	int l_max = L[i], r_max = R[i], f = 0; //L[i], R[i]分别表示左右两边比第i个数大的数 
		if(l_max == 1e6 && r_max == 1e6) st = i;
		else{
			if(l_max == 1e6) add(r_max, i);
			else if(r_max == 1e6) add(l_max, i);
			else{
				if(a[l_max] > a[r_max]) add(r_max, i);
				else add(l_max, i);
			}
		}
	}
	dfs(st); 
	for(int i = 1; i <= n; i++){
		if(i == 1) printf("%d", cnt[i]);
		else printf(" %d", cnt[i]);
	}
	printf("\n");
	return 0;
} 
/*
7
1 3 2 7 5 6 4
*/

膜拜大佬……

T3 钦定IOI选手

手动粘题3.0

20pts做法

本题很显然是让我们维护一个动态中位数,所以很显然就能想到对顶堆。

#define p_q priority_queue
p_q < int , vector < int > , less < int > > qq1;//大根堆,堆顶存中位数
p_q < int , vector < int > , greater < int > > qq2;//小根堆
int ans = -1;
for(int l = 1; l <= n - k + 1; l ++) {//左端点
	qq1.push(a[l]);
	for(int r = l + 1; r <= n; r ++) {//右端点
		if(a[r] > qq1.top()) qq2.push(a[r]);
		else qq1.push(a[r]);
		int len = r - l + 1;
		while(qq1.size() > (len + 1) / 2) {
			qq2.push(qq1.top());
			qq1.pop();
		}
		while(qq1.size() < (len + 1) / 2) {
			qq1.push(qq2.top());
			qq2.pop();
		}
		if(len >= k) ans = max(ans, qq1.top());
	}
	while(!qq1.empty()) qq1.pop();
	while(!qq2.empty()) qq2.pop();
}
printf("%d", ans);

但很显然,它的时间复杂度是 O ( n 2 l o g   n ) O(n^2log~n) O(n2log n)的,无法 A C AC AC

100pts做法

看到中位数,显然与数的绝对大小无关,只与相对大小有关。考虑将每个数进行一些转化。对于一个数 x x x
,容易想到将 ≤ x \le x x 的都改为 − 1 -1 1 > x >x >x 的都改为 1 1 1,得到新数列(这里称作 b i b_i bi)。此时便有一个结
论:对于区间 [ l , r ] [l, r] [l,r],如果满足 ∑ i = 1 n b i > 0 \sum \limits_{i = 1}^{n} b_i >0 i=1nbi>0,则 [ l , r ] [l, r] [l,r]的中位数必定 ≥ x \ge x x(可以从与 x x x的相对
大小角度想)。

因此考虑每次二分 x x x,记录 b b b数组的前缀和和前缀最小值,在区间长度 ≥ k \ge k k时差分计算一下即可。

bool check(int x) {
	for(int i = 1; i <= n; i ++) {
		if(a[i] >= x) b[i] = 1;
		else b[i] = -1;
		sum[i] = sum[i - 1] + b[i];
	}
	int minn = 1e9;
	for(int i = k; i <= n; i ++) {
		minn = min(minn, sum[i - k]);//前缀最小值
		if(sum[i] - minn > 0) return 1;//只要区间和大于0,那么x就有可能成为此区间的中位数
	}
	return 0;
}

怒提 20 p t s 20pts 20pts

T4 攻打恶魔之巅

手动粘题4.0
这道题刚拿到就觉得是个 D P DP DP。关于这个 D P DP DP,我写挂了,但没完全写挂。

水水版DP

这道题的 D P DP DP状态设置很容易就能想到: d p i , j dp_{i,j} dpi,j表示走了 i i i步,花费了 j j j个魔法石以后的最小体力。这显然是以魔法石的数量来划分的阶,所以应把第二维的循环放在最外层,这样才能避免后效性。~~但是我没想到,就给他放到了最内层……~~另外,为了得到最优情况,我们还要正反各来一遍。具体原因见下图:
原因
在这种情况下,最优解是:从 1 1 1走一步到 3 3 3,然后花一颗魔法石到 8 8 8,接着往回跳到 6 6 6,再使用第二颗魔法石到终点。但是如果不往后跳,是无法在两步以内到终点的。但是我没想到,只正着跑了……

由此可见,此题细节之多,数据之水
A C AC AC代码:

#include<bits/stdc++.h>
using namespace std;

const int maxn = 5e5 + 5;
int n, m, c;
int t[maxn];
int dp[maxn][23];

int main() {
	scanf("%d%d%d", &n, &m, &c);
	for(int i = 1; i <= n; i ++) scanf("%d", &t[i]);
	memset(dp, 0x7f, sizeof dp);
	dp[1][0] = 0;
	for(int k = 0; k <= c; k ++) {
		for(int i = 1; i <= n; i ++) {
			for(int j = 1; j <= m; j ++) {
				if(i + j <= n) dp[i + j][k] = min(dp[i + j][k], dp[i][k] + 1);
				if(k < c) dp[t[i]][k + 1] = min(dp[i][k], dp[t[i]][k + 1]);
			}
		}
		for(int i = n; i >= 1; i --) {
			for(int j = 1; j <= m; j ++) {
				if(i - j >= 1) dp[i - j][k] = min(dp[i - j][k], dp[i][k] + 1);
				if(k < c) dp[t[i]][k + 1] = min(dp[i][k], dp[t[i]][k + 1]);
			}
		}
	}
	int ans = 0x7f7f7f7f;
	for(int k = 0; k <= c; k ++)
		ans = min(ans, dp[n][k]);
	printf("%d", ans);
	return 0;
}

侥幸提 95 p t s 95pts 95pts

T5 抉择

本题不在本蒟蒻的知识范围内……

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值