P10059 Choose
by Ecrade_
结论: 记长度为 时对应的 最大值为 ,则 单调不降。
证明:
对于任意长度 ,记 所有长度为 的子序列的极差依次为 ,记 从大到小排序后所的序列为 ,则 ;同样地,记 所有长度为 的子序列的极差依次为 ,记 从大到小排序后所的序列为 ,则 。
故我们仅需证明 即可。而不难发现,, 且 ,由此可推出 中至少有 个数不小于 ,进一步推出 。
故命题 成立,即 单调不降。
故 最大值即为 ,极差可用单调队列求得。 最小值有两种方法求。
法一
直接上二分,具体实现见代码。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
int t,n,k,a[100009];
int qs[100009],qb[100009];
inline int read(){
int s = 0,w = 1;
char ch = getchar();
while (ch > '9' || ch < '0'){ if (ch == '-') w = -1; ch = getchar();}
while (ch <= '9' && ch >= '0') s = (s << 1) + (s << 3) + (ch ^ 48),ch = getchar();
return s * w;
}
int work(int x,int lim,bool opt){
int res = 2e9,cnt = 0,hs = 1,ts = 0,hb = 1,tb = 0;
for (int i = 1;i <= n;i += 1){
while (ts >= hs && i - qs[hs] + 1 > x) hs += 1;
while (tb >= hb && i - qb[hb] + 1 > x) hb += 1;
while (ts >= hs && a[i] < a[qs[ts]]) ts -= 1;
while (tb >= hb && a[i] > a[qb[tb]]) tb -= 1;
qs[++ ts] = i,qb[++ tb] = i;
if (opt == 0 && i >= x) res = min(res,a[qb[hb]] - a[qs[hs]]);
if (opt == 1 && i >= x && a[qb[hb]] - a[qs[hs]] >= lim) cnt += 1;
if (opt == 1 && cnt >= k) break;
}
if (opt == 0) return res;
if (cnt < k) return 0;
return 1;
}
int main(){
t = read();
while (t --){
n = read(),k = read();
for (int i = 1;i <= n;i += 1) a[i] = read();
int xans = work(n - k + 1,0,0),lans = n - k + 1,l = 1,r = n - k + 1;
while (l <= r){
int mid = (l + r) >> 1;
if (work(mid,xans,1)) lans = mid,r = mid - 1;
else l = mid + 1;
}
printf("%d %d\n",xans,lans);
}
return 0;
}
法二
考虑对于每个长度 ,求出极差不小于 最大值的区间个数 。
从小到大枚举 ,求出以 为左端点的极差不小于 最大值的极短连续区间,记该区间右端点为 ,则对所有 都执行 。
具体实现上,由于所有极短连续区间均无包含关系,故可使用单调队列 + 双指针求得;由于每次执行 的 都是一段连续区间,故可用前缀和记录。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
int t,n,k,a[100009],cnt[100009];
int qs[100009],qb[100009];
inline int read(){
int s = 0,w = 1;
char ch = getchar();
while (ch > '9' || ch < '0'){ if (ch == '-') w = -1; ch = getchar();}
while (ch <= '9' && ch >= '0') s = (s << 1) + (s << 3) + (ch ^ 48),ch = getchar();
return s * w;
}
int main(){
t = read();
while (t --){
n = read(),k = read();
for (int i = 1;i <= n;i += 1) a[i] = read(),cnt[i] = 0;
int xans = 2e9,lans = 0,hs = 1,ts = 0,hb = 1,tb = 0,r = 0,all = 0;
for (int i = 1;i <= n;i += 1){
while (ts >= hs && i - qs[hs] > n - k) hs += 1;
while (tb >= hb && i - qb[hb] > n - k) hb += 1;
while (ts >= hs && a[i] < a[qs[ts]]) ts -= 1;
while (tb >= hb && a[i] > a[qb[tb]]) tb -= 1;
qs[++ ts] = i,qb[++ tb] = i;
if (i >= n - k + 1) xans = min(xans,a[qb[hb]] - a[qs[hs]]);
}
hs = 1,ts = 0,hb = 1,tb = 0;
for (int i = 1;i <= n;i += 1){
while (ts >= hs && qs[hs] < i) hs += 1;
while (tb >= hb && qb[hb] < i) hb += 1;
while (r < n && (ts < hs || tb < hb || a[qb[hb]] - a[qs[hs]] < xans)){
r += 1;
while (ts >= hs && a[r] < a[qs[ts]]) ts -= 1;
while (tb >= hb && a[r] > a[qb[tb]]) tb -= 1;
qs[++ ts] = r,qb[++ tb] = r;
}
if (a[qb[hb]] - a[qs[hs]] < xans) break;
cnt[r - i + 1] += 1,cnt[n - i + 2] -= 1;
}
for (int i = 1;i <= n;i += 1) cnt[i] += cnt[i - 1];
while (cnt[lans] < k) lans += 1;
printf("%d %d\n",xans,lans);
}
return 0;
}
by _JF_
沿袭上面的做法,其实验题人在二分完 后采用数据结构维护区间最值来进行检验是否合法。(可能好像写,好像赛时大部分人也是这么写的)
这样做的复杂度是 的,没有卡掉,也可以通过。
#include<bits/stdc++.h>
using namespace std;
const int N =1e5+10;
int F[N][20],f[N][20],dis[N],a[N],Log[N],n,k;
inline int read()
{
register int x=0,f=0;
register char t=getchar();
while(t<'0'||t>'9')f^=(t=='-'),t=getchar();
while(t>='0'&&t<='9')x=(x<<3)+(x<<1)+(t^48),t=getchar();
return f?-x:x;
}
bool cmp(int a,int b){
return a>b;
}
int QueryMax(int l,int r){
int k=Log[r-l+1];
return max(f[l][k],f[r-(1<<k)+1][k]);
}
int QueryMin(int l,int r){
int k=Log[r-l+1];
// cout<<l<<" "<<r<<" "<<k<<" "<<F[l][k]<<endl;
return min(F[l][k],F[r-(1<<k)+1][k]);
}
void init(){
memset(f,-0x3f,sizeof(f)),memset(F,0x3f,sizeof(F));
// for(int j=0;j<=Log[n];j++)
// for(int i=0;i+(1<<j)-1<=n;i++)
// F[i][j]=INT_MAX,f[i][j]=-INT_MAX;
for(int i=1;i<=n;i++)
F[i][0]=a[i],f[i][0]=a[i];
for(int j=1;j<=Log[n];j++)
for(int i=1;i+(1<<j)-1<=n;i++){
f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
F[i][j]=min(F[i][j-1],F[i+(1<<(j-1))][j-1]);
}
}
int check(int len){
int p=0;
for(int i=1;i+len-1<=n;i++){
int maxx=QueryMax(i,i+len-1),minn=QueryMin(i,i+len-1);
dis[++p]=maxx-minn;
// cout<<i<<" "<<i+len-1<<" "<<maxx<<" "<<minn<<endl;
}
if(p<k) return -1;
sort(dis+1,dis+p+1,cmp);
return dis[k];
}
void Find(int pre){
int l=1,r=n,AnsL=INT_MAX;
while(l<=r){
int mid=(l+r)>>1,now=check(mid);
if(now==-1) r=mid-1;
else if(now<pre) l=mid+1;
else if(now==pre) AnsL=min(AnsL,mid),r=mid-1;
// cout<<Ans<<" "<<AnsL<<endl;
}
cout<<pre<<" "<<AnsL<<endl;
}
int main(){
for(int i=2;i<=1e5;i++) Log[i]=Log[i>>1]+1;
int t;
t=read();
while(t--){
n=read(),k=read();
for(int i=1;i<=n;i++) a[i]=read();
init();
// cout<<check(3)<<endl;
Find(check(n-k+1));
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现