原题页面:https://ac.nowcoder.com/acm/contest/65194

Statements & Solution : https://www.luogu.com.cn/problem/U507978

\(80+80+50+24=234\)。

A

贪心+双指针。

根据贪心思想,大值选大、小值选小。我们按绝对值从大到小给\(a\)排序,再按从小到大给\(b\)排序,取双指针\(l=1,r=m\)。

从左往右遍历\(a[i]\),如果\(a[i]>0\)则选\(b[r]\)配对,否则选\(b[l]\)配对,配对后指针相应移动即可。

时间复杂度\(O(n)\)。

不放赛时代码了。赛时想复杂了,代码实现不是很简洁,而且暂时无从得知为什么只拿了\(80\text{ pts}\),已经对拍了上千组样例了(^^;

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define N 100010
#define M 100010
using namespace std;
int n,m,a[N],b[M],ans;
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=m;i++) cin>>b[i];
int l=1,r=m;
sort(a+1,a+1+n,[](int a,int b){return abs(a)>abs(b);});
sort(b+1,b+1+m);
for(int i=1;i<=n;i++) ans+=a[i]*(a[i]>0?b[r--]:b[l++]);
cout<<ans<<"\n";
return 0;
}

B

最后\(20\)分钟才开窍打出来了\(80\text{ pts}\),当时要是时间再多点正解也出来了,主要是前面无效思考太多了,果然还得多练。

赛时\(80\text{ pts}\)思路

建立大小为\(m\)的线段树,每个节点维护\(sum,cnt\),即数的总和、以及出现的总次数。

对于询问\(i\),将\(a[1\sim (i-1)]\)压进线段树中。我们想找的是“压入的所有元素中,最多能选出多少个,使得它们的和\(\le V\)”,其中\(V=m-a[i]\)。

为了让个数尽可能多,我们肯定优先选尽可能小的元素,而我们是按值域建树的,所以从根节点开始。先考虑左子树,如果左子树的\(sum(lson)\le V\),那就跳到左子树里找\(V\);否则跳到右子树中找\(V-sum(lson)\),再把答案加上\(cnt(lson)\)。代码是这样的:

int query(int v,int x,int l,int r){//[l,r]值域范围内,和<=v最多能选多少个
if(l==r) return min(cnt[x],v/l);
int mid=(l+r)>>1;
if(sum[lc(x)]<=v) return cnt[lc(x)]+query(v-sum[lc(x)],rc(x),mid+1,r);
else return query(v,lc(x),l,mid);
}

时间复杂度\(O(T(n+m)\log m)\)。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define lc(x) ((x)<<1)
#define rc(x) ((x)<<1|1)
#define N 100010
using namespace std;
int t,n,m,a[N],cnt[400010],sum[400010];
void update(int x){
cnt[x]=cnt[lc(x)]+cnt[rc(x)];
sum[x]=sum[lc(x)]+sum[rc(x)];
}
void build(int x,int l,int r){
if(l==r) return (void)(cnt[x]=sum[x]=0);
int mid=(l+r)>>1;
build(lc(x),l,mid),build(rc(x),mid+1,r);
update(x);
}
void chp(int a,int v,int v2,int x,int l,int r){
if(l==r) return (void)(cnt[x]+=v,sum[x]+=v2);
int mid=(l+r)>>1;
if(a<=mid) chp(a,v,v2,lc(x),l,mid);
else chp(a,v,v2,rc(x),mid+1,r);
update(x);
}
int query(int v,int x,int l,int r){//[l,r]值域范围内,和<=v最多能选多少个
if(l==r) return min(cnt[x],v/l);
int mid=(l+r)>>1;
if(sum[lc(x)]<=v) return cnt[lc(x)]+query(v-sum[lc(x)],rc(x),mid+1,r);
else return query(v,lc(x),l,mid);
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr),cout.tie(nullptr);
cin>>t;
while(t--){
cin>>n>>m;
build(1,1,m);
for(int i=1;i<=n;i++){
cin>>a[i];
cout<<i-query(m-a[i],1,1,m)-1<<" ";
chp(a[i],1,a[i],1,1,m);
}
cout<<"\n";
}
return 0;
}

正解

这种写法显然有很多冗余空间,我们将值域离散一下不就好了嘛,只要里面的内容不改变就行了。

时间复杂度\(O(T n\log m)\),可以通过。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define lc(x) ((x)<<1)
#define rc(x) ((x)<<1|1)
#define N 100010
using namespace std;
int t,n,m,a[N],tmp[N],cnt[400010],sum[400010];
unordered_map<int,int> to;
void update(int x){
cnt[x]=cnt[lc(x)]+cnt[rc(x)];
sum[x]=sum[lc(x)]+sum[rc(x)];
}
void build(int x,int l,int r){
if(l==r) return (void)(cnt[x]=sum[x]=0);
int mid=(l+r)>>1;
build(lc(x),l,mid),build(rc(x),mid+1,r);
update(x);
}
void chp(int a,int v,int v2,int x,int l,int r){
if(l==r) return (void)(cnt[x]+=v,sum[x]+=v2);
int mid=(l+r)>>1;
if(a<=mid) chp(a,v,v2,lc(x),l,mid);
else chp(a,v,v2,rc(x),mid+1,r);
update(x);
}
int query(int v,int x,int l,int r){//[l,r]值域范围内,和<=v最多能选多少个
if(l==r) return min(cnt[x],v/tmp[l]);
int mid=(l+r)>>1;
if(sum[lc(x)]<=v) return cnt[lc(x)]+query(v-sum[lc(x)],rc(x),mid+1,r);
else return query(v,lc(x),l,mid);
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr),cout.tie(nullptr);
cin>>t;
while(t--){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i],tmp[i]=a[i];
sort(tmp+1,tmp+1+n);
int tn=unique(tmp+1,tmp+1+n)-tmp-1;
for(int i=1;i<=tn;i++) to[tmp[i]]=i;
build(1,1,tn);
for(int i=1;i<=n;i++){
cout<<i-query(m-a[i],1,1,tn)-1<<" ";
chp(to[a[i]],1,a[i],1,1,tn);
}
cout<<"\n";
}
return 0;
}

C

赛时\(50\text{ pts}\)思路

前\(50\text{ pts}\)比较的送:

  • \(20\text{ pts}\):\(k=1\)的特殊性质求前缀和最小值即可。
  • \(30\text{ pts}\):\(n\le 100\)可以打\(O(n^3)\) DP,令\(f[i][j]\)为\(a[1\sim i]\)分\(j\)段的答案,\(S\)为\(a\)的前缀和数组,那么有转移:
    \[f[i][j]=\min\limits_{k\in[i-1,j-1]}\Big(\max(f[k][j-1],S[i]-S[k])\Big)
    \]
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define N 100010
using namespace std;
int T,n,k,a[N],len[N],f[102][102];
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr),cout.tie(nullptr);
cin>>T;
while(T--){
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>a[i];
if(k==1){
int ans=LLONG_MAX;
for(int i=1;i<=n;i++){
a[i]+=a[i-1];
ans=min(ans,a[i]);
}
cout<<ans<<"\n";
continue;
}
for(int i=1;i<=n;i++) a[i]+=a[i-1];
memset(f,0x3f,sizeof f);
for(int i=1;i<=n;i++) f[i][1]=a[i];
for(int i=2;i<=n;i++)
for(int j=2;j<=i;j++)
for(int k=j-1;k<i;k++)
f[i][j]=min(f[i][j],max(f[k][j-1],a[i]-a[k]));
int ans=LLONG_MAX;
for(int i=k;i<=n;i++) ans=min(ans,f[i][k]);
cout<<ans<<"\n";
}
return 0;
}

正解

这道题很像是P1182 数列分段 Section II的加强版。

  • 首先存在负权,不能贪心计数。
  • 其次这道题可以对\(a\)的任意一个前缀进行分段。

但我们看到最大值最小化,还是首先想到二分蛀牙值的最大值\(mid\)。

我们用\(f[i]\)表示\(a[1\sim i]\)最多分多少段,使得每段和都\(\le mid\),有转移\(f[i]=\max(f[j]+1)\),其中\(j\)满足\(j<i,S[i]-S[j]\le mid\)。如果存在\(f[i]\ge K\)则合法(这是因为只要存在\(f[i]>K\),就一定存在\(j<i\)使得\(f[j]=K\))。

时间复杂度\(O(n^2\log V)\),时间开销主要在于\(O(n)\)的状态转移。

\(S[i]-S[j]\le mid\)移项得\(S[j]\ge S[i]-mid\)。我们考虑用\(S[u]\)作为索引,\(f[u]\)作为值,扔到线段树里。这样我们更新的时候求线段树\((S[i]-mid)\sim V\)的最大值\(maxx\),用\(maxx+1\)更新\(f[i]\)就可以了。另外,为了保证\(j<i\),我们必须顺序遍历,这样才能保证计算\(f[i]\)时只有\(f[1\sim (i-1)]\)被扔进了线段树(况且不顺序遍历就无法递推了)。

但\(V\)实在是太大,因此我们需要将所有用到的下标(即\(S[i]\)与所有\(S[i]-mid\))离散化。

就酱。

时间复杂度降为\(O(n\log n\log V)\)。


不过线段树常数太大会被卡成\(80\text{ pts}\),可以用树状数组来代替,虽然它无法直接维护区间最大值,但我们发现每次询问求的都是后缀和,所以可以直接把树状数组反着建,这样每次查询后缀就可以了~

线段树
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#define int long long
#define lc(x) ((x)<<1)
#define rc(x) ((x)<<1|1)
#define N 100010
using namespace std;
using namespace __gnu_pbds;
int t,n,k,a[N],maxx[N<<3],v[N<<1],f[N],idx;
gp_hash_table<int,int> ma;
void update(int x){maxx[x]=max(maxx[lc(x)],maxx[rc(x)]);}
void build(int x,int l,int r){
maxx[x]=LLONG_MIN;
if(l==r) return;
int mid=(l+r)>>1;
build(lc(x),l,mid),build(rc(x),mid+1,r);
}
void chp(int a,int v,int x,int l,int r){
if(l==r) return (void)(maxx[x]=max(maxx[x],v));
int mid=(l+r)>>1;
if(a<=mid) chp(a,v,lc(x),l,mid);
else chp(a,v,rc(x),mid+1,r);
update(x);
}
int query(int a,int b,int x,int l,int r){
if(a<=l&&r<=b) return maxx[x];
int mid=(l+r)>>1,ans=LLONG_MIN;
if(a<=mid) ans=max(ans,query(a,b,lc(x),l,mid));
if(b>mid) ans=max(ans,query(a,b,rc(x),mid+1,r));
return ans;
}
bool check(int mid){
idx=0,ma.clear();
for(int i=1;i<=n;i++) v[++idx]=a[i],v[++idx]=a[i]-mid;
v[++idx]=0;
sort(v+1,v+1+idx);
int tn=unique(v+1,v+1+idx)-v-1;
for(int i=1;i<=tn;i++) ma[v[i]]=i;
build(1,1,tn),chp(ma[0],(f[0]=0),1,1,tn);
for(int i=1;i<=n;i++){
f[i]=query(ma[a[i]-mid],tn,1,1,tn)+1;
if(f[i]>=k) return 1;
chp(ma[a[i]],f[i],1,1,tn);
}
return 0;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr),cout.tie(nullptr);
cin>>t;
while(t--){
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>a[i],a[i]+=a[i-1];
int l=-1e14,r=1e14;
while(l<r){
int mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
cout<<l<<"\n";
}
return 0;
}
树状数组
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#define int long long
#define N 100010
using namespace std;
using namespace __gnu_pbds;
int t,n,k,a[N],v[N<<1],f[N],idx,maxx[N<<1],tn;
gp_hash_table<int,int> ma;
inline int lowbit(int x){return x&-x;}
void init(){for(int i=1;i<=tn;i++) maxx[i]=LLONG_MIN;}
void opt(int x,int k){while(x) maxx[x]=max(maxx[x],k),x-=lowbit(x);}
int query(int x){int ans=LLONG_MIN;while(x<=tn) ans=max(ans,maxx[x]),x+=lowbit(x);return ans;}
bool check(int mid){
idx=0,ma.clear();
for(int i=1;i<=n;i++) v[++idx]=a[i],v[++idx]=a[i]-mid;
v[++idx]=0;
sort(v+1,v+1+idx);
tn=unique(v+1,v+1+idx)-v-1;
for(int i=1;i<=tn;i++) ma[v[i]]=i;
init(),opt(ma[0],(f[0]=0));
for(int i=1;i<=n;i++){
f[i]=query(ma[a[i]-mid])+1;
if(f[i]>=k) return 1;
opt(ma[a[i]],f[i]);
}
return 0;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr),cout.tie(nullptr);
cin>>t;
while(t--){
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>a[i],a[i]+=a[i-1];
int l=-1e14,r=1e14;
while(l<r){
int mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
cout<<l<<"\n";
}
return 0;
}

这样想来,这道题如果没有了第二条“加强”还真不好做,因为不满足单调性,所以\(f_i\)成立 \(\nRightarrow f_i +1\)也成立。如果只有第一条“加强”,要求\(a\)的答案,就不能设\(f\)为最大值,而是要从前面所有可能的值进行转移,这样还得额外乘一个\(O(n)\)的时间复杂度。目前还想不到足够优秀的复杂度来解决这个新的问题。如果大家有想法,欢迎在评论区讨论,或者在此帖分享你的见解!

D

不补了(逃

div.postBody h2 { background-color: rgba(178, 178, 178, 0.13); background-image: url("https://images.cnblogs.com/cnblogs_com/blogs/807965/galleries/2432812/o_241122053313_vichy.png"); border-radius: 4px; transition: all 0.5s, background-position 3s }
div.postBody h2:hover { background-position: 120px 120px; padding-left: 16px; background-color: rgba(77, 77, 77, 0.13); color: rgba(255, 255, 255, 1) }

[题解][test06]2024/11/23 模拟赛 / 2023牛客OI赛前集训营-提高组(第三场) A~C的更多相关文章

  1. 2021牛客OI赛前集训营-提高组(第二场)第三题 树数树题解

    题目描述 牛牛有一棵 \(n\) 个点的有根树,根为 \(1\). 我们称一个长度为 \(m\) 的序列 \(a\) 是好的,当且仅当: \(\forall i \in (1,m]\),\(a_i\) ...

  2. 2021牛客OI赛前集训营-提高组(第三场) 第二题 交替 题解与结论证明

    题目描述 一个长度为 \(n\) 的数组\(A\),每秒都会变成一个长度为 \(n − 1\) 新数组 \(A'\),其变化规 则如下: 若当前数组 \(A\) 的长度 \(n\) 为偶数,则对于新数 ...

  3. 2020牛客NOIP赛前集训营-普及组(第二场) 题解

    目录 T1 面试 描述 题目描述 输入描述: 输出描述: 题解 代码 T2 纸牌游戏 描述 题目描述 输入描述: 输出描述: 题解 代码 T3 涨薪 描述 题目描述 输入描述: 输出描述: 题解 代码 ...

  4. Nowcoder | [题解-N165]牛客网NOIP赛前集训营-普及组(第二场)

    啊...表示一大早还没睡醒就开始打比赛(开始前一分钟的我还在桌子上趴着休眠)...表示题目思路清奇(尤其C题)...但是我还是太蒻了...\(D\)题暴力都没打...题解正式开始之前先\(\%\)一下 ...

  5. 比赛总结——牛客网 NOIP赛前集训营提高组模拟第一场

    第一场打的很惨淡啊 t1二分+前缀最小值没想出来,20分的暴力也挂了,只有10分 t2数位dp,调了半天,结果因为忘了判0的特殊情况WA了一个点,亏死 t3emmmm.. 不会 imone说是DSU ...

  6. [NowCoder]牛客网NOIP赛前集训营-提高组(第六场)题解

    A.最长路 题意:给定有向图,每条边有个字符\([0,10^9]\),求每个点最长路字典序最小的方案.\(N,M\le 10^6\) 建反图跑拓扑排序,显然入过队的点都有最长路,考虑如何判断字典序大小 ...

  7. 9.11 myl模拟赛

    9.11 myl 模拟赛 100 + 100 + 0 第一题耗费了太多的时间,导致最后一题没有时间想,直接去写了暴力,而且出题人没有给暴力分.... Problem 1. superman [题目描述 ...

  8. 牛客OI赛制测试赛2(0906)

    牛客OI赛制测试赛2(0906) A :无序组数 题目描述 给出一个二元组(A,B) 求出无序二元组(a,b) 使得(a|A,b|B)的组数 无序意思就是(a,b)和(b,a) 算一组. 输入描述: ...

  9. 牛客OI月赛12-提高组题解

    牛客OI月赛12-提高组 当天晚上被\(loli\)要求去打了某高端oj部分原创的模拟赛,第二天看了牛客的题觉得非常清真,于是就去写了 不难发现现场写出\(260\text{pts}\)并不需要动脑子 ...

  10. NOIP 2008提高组第三题题解by rLq

    啊啊啊啊啊啊今天已经星期三了吗 那么,来一波题解吧 本题地址http://www.luogu.org/problem/show?pid=1006 传纸条 题目描述 小渊和小轩是好朋友也是同班同学,他们 ...

随机推荐

  1. 在 Mac 系统上使用 docker

    1.通过 brew 安装 docker client brew install docker 2.通过 brew 安装 colima(开源的轻量级容器,可以在 linux 和 mac 上运行) bre ...

  2. centos7.5安装mariadb

    一.安装mysql 这里我们基础系统是centos7.5 [root@monitor ~]# yum install -y mariadb-server [root@monitor ~]# syste ...

  3. 修改AndroidStudio的Boot Java Runtime for the IDE后,IDE打开报错无法运行

      修改AndroidStudio的Boot Java Runtime for the IDE后,IDE打开报错无法运行,解决方法 一.问题 我想在AndroidStudio里使用markdown支持 ...

  4. UFT API

  5. 使用laradock配置mysql主从同步

    主从原理 laradock配置主从 在laradock 目录下创建mysql2 作为slave库 ├── mysql //laradock 自带的目录 我们用它作为master │ ├── Docke ...

  6. apche服务器下无后缀文件配置浏览器访问自动下载

    1.在配置最新的IOS app 微信授权登录时 SDK时,碰到一个问题.服务器端需要配置IOS唤起微信APP授权的通用链接地址. 2.关于通用链接 3. 必须将ios的配置文件放入网址根目录下的app ...

  7. C# Winform 下 DataGridView 行(SelectedRows)转 模型 Model

    /// <summary> /// DataRow 转 Model /// </summary> /// <typeparam name="T"> ...

  8. frp内网穿透的搭建与使用

    什么是内网穿透 内网穿透,又称为NAT穿透. NAT穿透技术是让NAT背后的设备,先访问指定的外网服务器,由指定的外网服务器搭建桥梁,打通内.外网设备的访问通道,实现外网设备访问到内网设备. 总之就是 ...

  9. cordova封装一个vue应用

    前言 目前webApp的实现方式有很多种,但是大致分为一下技术类型编译增强性:编译html成不同平台的源代码,将其运行在各个平台上,对应框架有 taro.uiniapp.reactNactiveweb ...

  10. k30s刷入国际rom

    先看看效果图 第一步:先解锁BL这个百度,或者官网有工具http://www.miui.com/unlock第二步:下载国际服ROM官网即可https://en.miui.com/下载国际版的哪个版本 ...