洛谷笔记 Day 4(1)
T1
解法
我们先考虑最后最大值一定是单调不降的,那么我们就可以维护一个栈里面去放所有的后缀最大值,接着你考虑当我新加入一个值时,是有可能去覆盖原先一些后缀最大值的,那么我们就把小于当前值的栈里的数全部弹出,因为栈里是单调不降的,所以弹出的一定是原栈中的一段前缀。
考虑分析时间复杂度,每个数最多被进栈和出栈分别一次,所以时间复杂度 \(O(n)\)。
代码
vector 功能和常数全面碾压 stack。
#include<bits/stdc++.h>
#define int unsigned long long
using namespace std;
int s=0;
int a[1000005];
signed main(){
ios::sync_with_stdio(0);
int n;cin>>n;
vector<int>st;
for (int i=1;i<=n;++i)cin>>a[i];
for (int i=1;i<=n;++i){
while (!st.empty()&&a[st.back()]<=a[i]){s^=st.back();st.pop_back();}
st.push_back(i);s^=i;
cout<<s<<'\n';
}
}
T2
思路
这题和刚才的区别就是有一个 \(k\) 的限制,我们考虑栈中元素的下标是单调递增的,所以我们只要在前面也开一头去把过期的弹出就行了。
代码
#include<bits/stdc++.h>
#define int unsigned long long
using namespace std;
int a[1000005];
signed main(){
int n,k;cin>>n>>k;
for (int i=1;i<=n;++i)cin>>a[i];
deque<int>st;
for (int i=1;i<k;++i){
while (!st.empty()&&a[st.back()]<=a[i])st.pop_back();
st.push_back(i);
}
for (int i=k;i<=n;++i){
int l=i-k+1;
if (!st.empty()&&st.front()<l)st.pop_front();
while (!st.empty()&&a[st.back()]<=a[i])st.pop_back();
st.push_back(i);
cout<<st.size()<<'\n';
}
}
T3
题意
找出最长的区间 \([l,r]\),使得 \(a_l=\min_{i\in{l,r}} a_i\),\(a_r=\max_{i\in{l,r}} a_i\)。
题解
如果我们只考虑 \([1,r]\) 这个区间,那么会发现 \(l\) 一定是这个区间的后缀最小值,接着来考虑 \(r\) 的限制,因为 \(r\) 要为 \([l,r]\) 最大,所以就要找打第一个比 \(r\) 大的数,即其后面都是比 \(a_r\) 小的,\(l\) 也就只能在这个区间内。则我们考虑维护两个单调栈,一个处理前面第一个比 \(r\) 大的,另一个处理 \(r\) 的最靠前比它大了,维护出来二分即可。
另外一种做法我们考虑分治,定义 \(solve(l,r)\) 表示处理 \([l,r]\) 区间的答案,考虑如何向下分治,我们用 ST 表维护每个区间的最大值位置 \(y\) 和最小值位置 \(x\),分讨即可。具体细节见代码。
代码
// 单调栈
#include<bits/stdc++.h>
using namespace std;
int a[100005];
int main(){
int n;cin>>n;
for (int i=1;i<=n;++i)cin>>a[i];
vector<int>stm,stx;
int ans=0;
for (int i=1;i<=n;++i){
while (!stm.empty()&&a[stm.back()]>=a[i])stm.pop_back();
while (!stx.empty()&&a[stx.back()]<=a[i])stx.pop_back();
int p=0;
if (!stx.empty()) p=stx.back();
int l=upper_bound(stm.begin(),stm.end(),p)-stm.begin();
if (l<stm.size()){
if (a[stm[l]]<a[i])ans=max(ans,i-stm[l]+1);
}
stm.push_back(i);
stx.push_back(i);
}
cout<<ans;
}
// 分治
#include<bits/stdc++.h>
using namespace std;
int a[100005],st1[100005][21],st2[100005][21];
int qmax(int l,int r){
if(l>r)return 0;
int x=__lg(r-l+1);
int ll=st1[l][x],rr=st1[r-(1<<x)+1][x];
return a[ll]>a[rr]?ll:rr;
}
int qmin(int l,int r){
if(l>r)return 0;
int x=__lg(r-l+1);
int ll=st2[l][x],rr=st2[r-(1<<x)+1][x];
return a[ll]<a[rr]?ll:rr;
}
int ans=0;
void solve(int l,int r){
if(l>=r)return;
int x=qmin(l,r),y=qmax(l,r);
if(!x||!y)return;
if(x<y){
if(a[y]>a[x])ans=max(ans,y-x+1);
solve(l,x-1),solve(y+1,r);
}
else{
solve(l,y),solve(y+1,x-1),solve(x,r);
}
}
int main(){
int n;cin>>n;
for(int i=1;i<=n;++i)cin>>a[i];
for(int i=1;i<=n;++i){
st1[i][0]=i;
st2[i][0]=i;
}
for(int j=1;j<=20;++j){
for(int i=1;i+(1<<j)-1<=n;++i){
if(a[st1[i][j-1]]>a[st1[i+(1<<(j-1))][j-1]])st1[i][j]=st1[i][j-1];
else st1[i][j]=st1[i+(1<<(j-1))][j-1];
if(a[st2[i][j-1]]<a[st2[i+(1<<(j-1))][j-1]])st2[i][j]=st2[i][j-1];
else st2[i][j]=st2[i+(1<<(j-1))][j-1];
}
}
solve(1,n);
cout<<ans;
}
T4
思路
一个经典 trick 缩矩阵,我们考虑两次处理,第一次把每行所有长度为 \(a\) 的最大值和最小值缩成一个点,第二次缩列,最后你会发现缩完的矩阵就是每个大小固定子矩阵的最大值以及最小值。单调队列维护即可。
代码
#include<bits/stdc++.h>
using namespace std;
int a[1005][1005];
int maxn[1005][1005],minn[1005][1005];
int main(){
int a,b,n;cin>>a>>b>>n;
for (int i=1;i<=a;++i){
for (int j=1;j<=b;++j){
cin>>::a[i][j];
}
}
for (int j=1;j<=a;++j){
deque<int>q1;
for (int i=1;i<=b;++i){
while (!q1.empty()&&::a[j][q1.back()]>=::a[j][i])q1.pop_back();
q1.push_back(i);
while (!q1.empty()&&q1.front()<=i-n)q1.pop_front();
if (i>=n){
minn[j][i]=::a[j][q1.front()];
}
}
}
for (int j=1;j<=a;++j){
deque<int>q;
for (int i=1;i<=b;++i){
while (!q.empty()&&::a[j][q.back()]<=::a[j][i])q.pop_back();
q.push_back(i);
while (!q.empty()&&q.front()<=i-n)q.pop_front();
if (i>=n){
maxn[j][i]=::a[j][q.front()];
}
}
}
int ans=INT_MAX;
for (int j=n;j<=b;++j){
deque<int>q1,q2;
for (int i=1;i<=a;++i){
while (!q1.empty()&&minn[q1.back()][j]>=minn[i][j])q1.pop_back();
while (!q2.empty()&&maxn[q2.back()][j]<=maxn[i][j])q2.pop_back();
q1.push_back(i);
q2.push_back(i);
while (!q1.empty()&&q1.front()<=i-n)q1.pop_front();
while (!q2.empty()&&q2.front()<=i-n)q2.pop_front();
if (i>=n){
ans=min(ans,maxn[q2.front()][j]-minn[q1.front()][j]);
}
}
}
cout<<ans<<endl;
}
笛卡尔树
理论
感觉学的不是很好的一个东西,概念就是值域满足堆性质,编号满足 BST性质状物。
接着我们考虑如何建树,每次新加入一个点后我们把它放在最右链一保障概念的性质,接下来上浮,直到满足堆性质,假设上浮完 \(u\) 是加入节点的父节点,我们就把 \(u\) 的右子树改成新加入的点,把 \(u\) 原来的右子树放到新加入节点的左子树。可以单调栈维护。\(O(n)\)。
代码
// LGP5854
#include<bits/stdc++.h>
using namespace std;
int a[10000005],l[10000005],r[10000005];
int main(){
ios::sync_with_stdio(0);
int n;cin>>n;
for (int i=1;i<=n;++i)cin>>a[i];
vector<int>st;
for (int i=1;i<=n;++i){
int last=0;
while (!st.empty()&&a[st.back()]>a[i]){
last=st.back();
st.pop_back();
}
if (!st.empty())r[st.back()]=i;
l[i]=last;
st.push_back(i);
}
unsigned long long ans1=0,ans2=0;
for (int i=1;i<=n;++i){
ans1^=1ull*i*(l[i]+1);
ans2^=1ull*i*(r[i]+1);
}
cout<<ans1<<" "<<ans2;
}
T5
思路
考虑悬线法,定义 \(l_{i,j}\) 表示 \((i,j)\) 位置最多往上连多少个 \(1\),递推预处理是简单的,考虑怎么利用这个东西求答案,我们嵌定 \(l_{i,j}\) 为最矮的,那么对每行建立一个笛卡尔树,答案就是 \(sz_{i,j} \times l_{i,j}\)。
代码没写qwq。

浙公网安备 33010602011771号