CF547B - Description
给你个长度为 \(n\) 的序列 \(a\),对于每个 \(1\le k\le n\),有 \(n-k+1\) 个中所有长度为 \(k\) 的子串,你需要求出这 \(n-k+1\) 个子串的区间最小值的最大值,即下面式子的值:
\(1\le n\le 2\times 10^5\)。
CF547B - Solution
首先考虑转化这个问题。假设有 \(l\le i\le r\),其中 \(a_i\) 是区间 \(\left [ l,r\right ]\) 的最小值,那么 \(a_i\) 一定具有这样一个性质:
- 对于所有 \(j<i\),若 \(a_i<a_j\),则有 \(j<l\)。
- 对于所有 \(j>i\),若 \(a_i<a_j\),则有 \(j>r\)。
针对这个性质,我们可以在 \(k\) 为定值时将问题转化为一个判定性问题。假设我们目前求出了 \(lft_i\) 和 \(rgt_i\) 来分别表示在 \(i\) 前面第一个小于 \(a_i\) 的数的位置和在 \(i\) 后面的第一个小于 \(a_i\) 的数的位置,那么 \(a_i\) 可以是 $\left [ lft_i+1,rgt_i-1\right ] $ 中任意一个子区间的区间最小值,因此 \(a_i\) 会对所有 \(k\le rgt_i-lft_i-1\) 的答案做出贡献(下面将记作 \(b_i\))。这里的 \(rgt\) 和 \(lft\) 我们可以使用单调栈 \(O(n)\) 求出,而我们注意到对所有元素从大到小排序,第一个扫到的 \(b_i\) 一定就是最大的,后面不会再被更新,于是我们将 \((b_i,a_i)\) 存为二元组后对 \(a_i\) 从大到小排序。排完序扫一遍,如果每次从上一个更新后的 \(b_{lst}+1\) 开始扫,扫到 \(b_i\),中间所有答案都赋值为 \(a_i\)。因为所有位置都只会被扫到一次,所以时间复杂度为 \(O(n)\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
long long n,a[200005],lft[200005],rgt[200005],ava[200005],ans[200005],lst;
stack<int>q,qq;
struct node{int val,av;
}e[200005];
inline bool cmp(node x,node y){if(x.val==y.val){return x.av>y.av;}return x.val>y.val;
}
signed main(){cin>>n;for(int i=1;i<=n;i++){cin>>a[i];}for(int i=1;i<=n;i++){while(!q.empty()&&a[q.top()]>a[i]){rgt[q.top()]=i;q.pop();}q.push(i);}for(int i=n;i>=1;i--){while(!qq.empty()&&a[qq.top()]>a[i]){lft[qq.top()]=i;qq.pop();}qq.push(i);}for(int i=1;i<=n;i++){if(rgt[i]==0){rgt[i]=n+1;}ava[i]=rgt[i]-lft[i]-1;e[i].val=a[i];e[i].av=ava[i];}sort(e+1,e+1+n,cmp);for(int i=1;i<=n;i++){int val=e[i].val;for(int j=e[lst].av+1;j<=e[i].av;j++){lst=i;ans[j]=val;}}for(int i=1;i<=n;i++){cout<<ans[i]<<" ";}return 0;
}
ABC352D - Description
给你一个 \(n\) 的排列 \(a\),让你选出一个长度为 \(k\) 的 \(a\) 的子序列 \(b=\left [ a_{p_1},a_{p_2},\cdots ,a_{p_k} \right ]\),使得 \(\min b_i +k-1=\max b_i\) 的同时控制 \(p_k-p_1\) 最小,求出这个最小值。
\(1\le n,k\le 2\times 10^5\)。
ABC352D - Solution
注意到 \(a\) 数组本身就是排列,而要求选出排列就等于直接截取 \(a\) 的子串,这省去了中间的很多过程。先求出 \(c_{a_i}=i\),可以发现 \(c\) 是 \(a\) 的一个置换,因此显然不影响答案。要让 \(b\) 数组构成一个排列 \(\{ a,a+1,\cdots ,a+k-1\}\),就是在 \(c\) 中选出一个长度为 \(k\) 的子串。而 \(p_k\) 就是这个子串中的最大 \(c_i\),\(p_1\) 就是最小 \(c_i\)。现在,答案就变成了下面式子的值:
单调队列扫描可以做到 \(O(n)\) 预处理。
#include<bits/stdc++.h>
#define int long long
using namespace std;
long long n,k,a[200005],c[200005],minn[200005],maxx[200005],ans=INT_MAX;
deque<int>q;
signed main(){cin>>n>>k;for(int i=1;i<=n;i++){cin>>a[i];c[a[i]]=i;}
// for(int i=1;i<=n;i++){
// cout<<c[i]<<" ";
// }
// cout<<endl;for(int i=1;i<=n;i++){while(!q.empty()&&i-q.front()>=k){q.pop_front();}while(!q.empty()&&c[q.back()]>c[i]){q.pop_back();}q.push_back(i);minn[i]=c[q.front()];}while(!q.empty()){q.pop_back();}for(int i=1;i<=n;i++){while(!q.empty()&&i-q.front()>=k){q.pop_front();}while(!q.empty()&&c[q.back()]<c[i]){q.pop_back();}q.push_back(i);maxx[i]=c[q.front()];}for(int i=1;i<=n-k+1;i++){int x=i+k-1;ans=min(ans,maxx[x]-minn[x]);}cout<<ans<<endl;return 0;
}
CF1407D - Description
有 \(n\) 栋楼,每栋楼有高度 \(h_i\),对于第 \(i\) 栋楼和第 \(j\) 栋楼,如果 \((i,j)\) 满足以下三个条件中的任意一个,我们认为可以从第 \(i\) 栋楼跳到第 \(j\) 栋楼:
- \(i+1=j\)
- \(\max(h_{i+1},h_{i+2},\cdots ,h_{j-1})<\min(h_i,h_j)\)
- \(\min(h_{i+1},h_{i+2},\cdots ,h_{j-1})>\max (h_i,h_j)\)
问你从第 \(1\) 栋楼开始,最少跳几次能跳到第 \(n\) 栋楼?
\(2\le n\le 3\times 10^5\)。
CF1407D - Solution
我们设计一个 \(f_i\) 表示从 \(1\) 走到 \(i\) 的最少步数。
对于限制 \(1\),容易得到 \(f_i=f_{i-1}+1\)。
限制 \(2\) 就是说对于所有 \(i<k<j\),\(i\) 都是 \(k\) 左侧第一栋比 \(h_k\) 高的楼,\(j\) 都是 \(k\) 右侧第一栋比 \(h_k\) 高的楼。这种左边/右边第一个大于/小于的题很容易想到单调栈求出 \(lft\) 和 \(rgt\)。
我们从 \(i\) 往前找,遍历每一个 \(h_j<h_i\) 的点,如果 \(j\) 满足 \(\max\{ h_{j+1},\cdots ,h_{i-1} \}<h_j\),就可以从 \(j\) 跳到 \(i\)。我们发现这个条件就等于在讲对于 \(i\) 更新单调栈时会弹出 \(j\),于是维护单调栈的时候顺手计算就行。一个细节是如果 \(h_j=h_i\),是无法从 \(j\) 跳到 \(i\) 的,注意特判。
#include<bits/stdc++.h>
#define int long long
using namespace std;
long long n,h[300005];
stack<int>q,qq;
long long dp[300005];
signed main(){cin>>n;for(int i=1;i<=n;i++){cin>>h[i];dp[i]=INT_MAX;}dp[1]=0;q.push(1);qq.push(1);for(int i=2;i<=n;i++){int lst=INT_MAX;while(!q.empty()&&h[q.top()]>=h[i]){dp[i]=min(dp[i],dp[q.top()]+1);lst=h[q.top()];q.pop();}if(!q.empty()&&lst!=h[i]){dp[i]=min(dp[i],dp[q.top()]+1);}q.push(i);lst=INT_MAX;while(!qq.empty()&&h[qq.top()]<=h[i]){dp[i]=min(dp[i],dp[qq.top()]+1);lst=h[qq.top()];qq.pop();}if(!qq.empty()&&lst!=h[i]){dp[i]=min(dp[i],dp[qq.top()]+1);}qq.push(i);}cout<<dp[n]<<endl;return 0;
}
P1080 - Description
有 \(n+1\) 个具备 \(a\) 与 \(b\) 两个数值的人(第 \(0\) 个人不参与计算,但是永远在所有人前面),对所有 \(1\le i\le n\),定义 \(f(i)\):
现在让你重新排列队伍,使最大的 \(f(i)\) 最小。(\(1\le i\le n\))
\(1\le n\le 1000,1\le a_i,b_i\le 10^4\)。
P1080 - Solution
像这种存在前后关系,并且操作满足交换律的题目我们应当警惕。这告诉我们这种贪心可以使用临项交换解决。具体地,我们考虑相邻的两项 \(i\) 和 \(i+1\),有:
我们考虑交换 \(i\) 和 \(i+1\) 对答案的影响。
如果 \(\arg \max f(i)\) 不是 \(i\) 或 \(i+1\),临项交换前后是对答案没有影响的;如果是 \(i\) 或 \(i+1\),我们发现交换操作就等同于给 \(f(i)\) 乘上 \(a_{i+1}\),给 \(f(i+1)\) 除以 \(a_i\)。如果 \(i\) 交换前更优,那么:
也就是说:
化简一下,我们得到了 \(a_ib_i<a_{i+1}b_{i+1}\) 的结论。也就是说,如果 \(a_ib_i<a_{i+1}b_{i+1}\),那么交换后一定是不优的。于是我们按照 \(a_ib_i\) 从小到大排序,扫描一遍得出答案即可。
#include<bits/stdc++.h>
#define int long long
using namespace std;
long long n,ans;
struct node{int a,b;
}e[1005];
inline bool cmp(node x,node y){return x.a*x.b<y.a*y.b;
}
signed main(){cin>>n;for(int i=0;i<=n;i++){cin>>e[i].a>>e[i].b;}sort(e+1,e+1+n,cmp);int fac=e[0].a;for(int i=1;i<=n;i++){ans=max(ans,fac/e[i].b);fac*=e[i].a;}cout<<ans<<endl;return 0;
}