暑假集训模拟赛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 堆人塔
这道题题意很容易就能搞明白:每次凭借区间中最大值将区间分为两个小区间,再分别不断取最大值、划分小区间……
显然是一道经典的递归题目。但是如果暴力去求每个区间的最大值显然是不行的,所以我们需要使用
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选手
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=1∑nbi>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 攻打恶魔之巅
这道题刚拿到就觉得是个
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 抉择
本题不在本蒟蒻的知识范围内……