------ From nfls Summer Camp
目录
- [T4 (easy+,98%)](#T4 (easy+,98%))
- [T5 (mid,40%)](#T5 (mid,40%))
- [T6 (mid,95%)](#T6 (mid,95%))
- [T7 (mid+,80%)](#T7 (mid+,80%))
- [T8 (hard,0%)](#T8 (hard,0%))
- 总结
T4 (easy+,98%)
link
思路
看到题目只是要求最后一个数,考虑只维护后面的数,序列长什么样没必要管。类似指针的做法,用一个变量模拟指针,记录每个新加的数的父亲,以及每个版本所存储的最后一个数的地址。
反思
从题目问题入手,题目操作很明确,可以直接利用指针模拟。着重往题目要求的的地方想,没必要记录的尽量抛弃。
代码
cpp
#include<bits/stdc++.h>
using namespace std;
const int maxq=5e5+5;
int a[maxq],fa[maxq];
string st;
map<int,int>mp;
int main(){
int q; cin>>q;
int now=0,cnt=0;
while(q--){
cin>>st;
if(st=="ADD"){
int x; cin>>x;
a[++cnt]=x,fa[cnt]=now;
now=cnt;
}
else if(st=="DELETE") now=fa[now];
else if(st=="SAVE"){
int x; cin>>x;
mp[x]=now;
}
else{
int x; cin>>x;
now=mp[x];
}
cout<<(now?a[now]:-1)<<" ";
}
return 0;
}
T5 (mid,40%)
link
题意
求一个由 .
和 #
组成的 n n n 行 m m m 列的矩阵中所有全是 .
的子矩形的面积和。
1 ≤ n , m ≤ 2000 1 \le n,m \le 2000 1≤n,m≤2000
思路
枚举每一行,考虑这一行和上面几行组成的子矩形对答案的贡献。
设枚举到位置 ( i , j ) (i,j) (i,j) ,计算以 ( i , j ) (i,j) (i,j) 为右下角所围成的子矩形的面积和。这需要知道第 i i i 行中每个位置向上连续的 .
的数量,记为 h i , j h_{i,j} hi,j ,这很好知道。然后考虑从第 j j j 列往前可以与那些列围城宽为 h i , j h_{i,j} hi,j 的子矩形,那就只需要知道离 j j j 最近的 < h i , j \lt h_{i,j} <hi,j 的位置即可,想到用 单调栈 维护 h i , j h_{i,j} hi,j 。然后就手动计算即可。复杂度均摊 O ( n m ) O(nm) O(nm) 。
反思
没有想到计算所有矩形的面积和可以拆开每一行处理,总结脑废了。注意在草稿纸上先理清思路,注意细节!!!
代码
cpp
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2005;
int stk[maxn];
ll h[maxn],f[maxn];
string st;
int main(){
int n,m; cin>>n>>m;
ll ans=0;
for(int i=1;i<=n;i++){
cin>>st;
for(int j=1;j<=m;j++){
if(st[j-1]=='.') h[j]++;
else h[j]=0;
}
int now=0;
for(int j=1;j<=m;j++){
while(now>=1&&h[stk[now]]>=h[j]) now--;
f[j]=f[stk[now]];
for(int k=now;k>=1;k--)
f[j]+=(1+h[stk[k]])*h[stk[k]]/2*(j-stk[now])*(stk[k]-stk[k-1]);
f[j]+=(1+h[j])*h[j]/2*(1+j-stk[now])*(j-stk[now])/2;
stk[++now]=j;
ans+=f[j];
}
}
cout<<ans;
return 0;
}
T6 (mid,95%)
link
思路
普通 DP + 线段树优化 。
反思
场上没注意到最大值可能为负,得了 〇 ,警钟!!!
T7 (mid+,80%)
题意
给一个数组 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an , 你希望固定尽量少的元素, 满足对于剩下未固定的元素 a x a_x ax , 它左右都有固定的元素。令 a l , a r a_l,a_r al,ar 是左右两边最靠近它的固定的元素, 能满足 a l ≤ a x ≤ a r a_l \le a_x \le a_r al≤ax≤ar 或 a l ≥ a x ≥ a r a_l \ge a_x \ge a_r al≥ax≥ar 。输出最少的固定元素的个数。 0 ≤ n ≤ 1 0 5 , 1 ≤ a i ≤ 1 0 9 0 \le n \le 10^5 ,1 \le a_i \le 10^9 0≤n≤105,1≤ai≤109
思路1
根据题目可以知道:数列中的最大最小值是一定要被固定的。证明很简单。那么夹在两个值中间的那段数就已经合法了。所以如果一个区间的数若两端分别是这些数的最大最小值,那么这个区间的数合法。扩展到剩下的两端的数,如果最大值已经固定那么就找最小值也固定,反之同理。可以递归处理答案,用线段树查找区间最大最小值即可。
具体可参考 这篇题解 ,懒得调代码了 。
思路2 (其实 思路1 更清晰易懂一点)
首先可以得到一个 O ( n 2 ) O(n^2) O(n2) 的 DP 暴力。设 f i f_i fi 表示前 i i i 个数保证合法下最少需要固定多少个元素(第 i i i 个必须选)。具体大致如下:
csharp
int n; cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
f[1]=1;
for(int i=1;i<n;i++){
int ma=0,mi=1e9;
for(int j=i+1;j<=n;j++){
if((a[i]<=mi&&ma<=a[j])||(a[j]<=mi&&ma<=a[i])) f[j]=min(f[j],f[i]+1);
else if(mi<a[i]&&a[i]<ma) break;
ma=max(ma,a[j]),mi=min(mi,a[j]);
}
}
cout<<f[n];
但其实再加上一点贪心,对于当前位置,找到左边最大的数的位置,再在这两个数绕出的区间中找到区间中的最小值,从最小值到当前位置这一段就都合法了。
反思
这道题其实不难,场上都没看,发现性质后就很好做了。所以最主要是根据题目和样例找到特定的有用的隐形条件。
T8 (hard,0%)
还没看也不想看......
总结
这场模拟赛其实也没有很难,但是主播代码能力真的太篛了,简单暴力都要调。
对题目的感知也没有很迅速,对题境的深入挖掘没有很准确。没睡够占一部分原因🤭
当然还要注意时间的把控。