CF1859 前四题题解
2023-08-13 18:17:37
前言
本来想着至少 A 四道的,但是 T4 因为我《卓越》的运用 stl 的能力挂掉了。不过还好,至少 rated 没掉(困死了困死了)。
A
题意:
把长度为 \(n\) 的数组 \(a\) 分成两个非空部分 \(b,c\) 使得 \(c\) 中任意数都不是 \(b\) 的因数,如果没有方案输出 -1,否则输出一组可行解。
思路:
因数肯定不能比原数大啊,所以直接让最大的几个跑 \(c\) 去就行了,注意 \(b,c\) 不能为空即可。
\(Code\)
int n,a[102];
int main(){
for(int T=read();T--;){
n=read();
int maxx=0;
for(int i=1;i<=n;i++){
a[i]=read();
maxx=max(a[i],maxx);
}
int o=0;
for(int i=1;i<=n;i++){
if(a[i]==maxx)o++;
}
if(o==n)puts("-1");
else{
printf("%d %d\n",n-o,o);
for(int i=1;i<=n;i++){
if(maxx!=a[i])printf("%d ",a[i]);
}
puts("");
for(int i=1;i<=o;i++)printf("%d ",maxx);
puts("");
}
}
}
B
观察到我们把每个数组的最小值集中到一个数组即可,然后答案就是其他数组次小值的和减去最小值,为了让这个值最大,我们把所有最小数丢进次小值最小的数组中即可,答案为 $$\sum_{i=1}^n secondmin_{j=1}ma_{i,j}-\min_{i=1}n secondmin_{j=1}^m a_{i,j}+\min_{i=1}^n \min_{j=1}^m a_{i,j}$$
\(Code\)
int n,a[25003];
int main(){
for(int T=read();T--;){
n=read();
int minn=2e9,minse=2e9;
ll sum=0;
for(int i=1;i<=n;i++){
int m=read(),fi=read(),se=2e9;
for(int j=1;j<m;j++){
int w=read();
if(w<=fi)se=fi,fi=w;
else if(w>fi&&w<se)se=w;
}
sum+=se;
minn=min(minn,fi);
a[i]=se;
}
for(int i=1;i<=n;i++){
minse=min(minse,a[i]-minn);
}
printf("%lld\n",sum-minse);
}
}
C
说实话不会证明,随手用 next_permutation() 打了个 \([1,10]\) 的表,然后就发现了规律:答案是从某个位置开始倒序排列的,如 \(10\) 的答案是 \(1,2,3,4,5,6,10,9,8,7\) 的序列。\(O(n^2)\) 枚举即可。
\(Code\)
int n,a[255];
int main(){
for(int T=read();T--;){
n=read();
ll ans=0;
for(int i=1;i<=n;i++){
for(int w=1;w<=i;w++)a[w]=w;
ll sum=0;int maxx=0;
for(int j=i;j<=n;j++){
a[j]=i-j+n;
}
for(int k=1;k<=n;k++)sum+=a[k]*k,maxx=max(maxx,a[k]*k);
sum-=maxx;
if(sum>ans)ans=sum;
}
printf("%lld\n",ans);
}
}
后话
没想到这样就直接过了,正解是 \(O(n^3)\) 可证明的,我觉得是可以直接 \(O(n)\) 求解的,好像评论区有人发了,不过我觉得复杂度反正够了就没有往那个方面想。
D
不要被最近学的图论优化给迷惑了!看到线段传送就往线段树优化建图上想,其实就是一个简单的线段合并,不过正解好像用的是排序后放 multiset 里用扫描线离线更新答案,复杂度跟我的方法一样是 \(O((n+q)\log n)\)。
注意到,我传送肯定是尽可能跑到 \(b_i\) 的位置,能跑到越后面的 \(b_i\) 就往后传送。不然我们假设这样做不是最优的,那么我们肯定可以通过某种方法从当前的 \(x_i\) 位置往后跳,跳到比 \(b_i\) 更后面但是 \(b_i\) 跳不到的地方,也就是有一个不包含 \(b_i\) 却包含 \(x_i\),然后此处的 \(b_j>b_i\) 的位置,显然不存在。
那既然知道了在一个区间内就一定会往后面的 \(b_i\) 跳,所以 \(a_i\) 和 \(r_i\) 就没有任何用处,注意到如果 \(b_i\) 被另外一个 \(l_j,b_j\) 的线段包含,那么我们显然可以跳到 \(b_j\) 可以跳到的地方。(这个时候我想到了用并查集维护,发现好像完全没必要)
所以我们可以直接把有重叠的 \([l_i,b_i]\) 线段直接合并成一个大线段,线段内所有位置的答案都是线段的最右处的坐标即可。合并线段可以先把线段按 \(l\) 排序,这样不会出现后来的线段包含先来的线段的情况,减少一些特判,然后用 lower_bound,找第一个大于等于 \(l\) 的 \(r\) 即可。查询时也是用 lower_bound 找第一个大于等于 \(x\) 的 \(r\),然后判断是否在线段内。
\(Code\)
struct line{
int l,r;
inline bool operator <(const line &w)const{
if(l==w.l)return r>w.r;
return l<w.l;
}
}a[200005];
map<int,int>S;
int n,q;
int main(){
for(int T=read();T--;){
n=read();
for(int i=1;i<=n;i++){
int l=read(),A=read(),b=read(),r=read();
a[i]={l,r};
}
S.clear();
sort(a+1,a+1+n);
for(int i=1;i<=n;i++){
auto loc=S.lower_bound(a[i].l);
if(/*loc==S.begin()||*/i==1||loc==S.end()){
S[a[i].r]=a[i].l;
}else{
if(a[i].r<=loc->first)continue;
S[a[i].r]=loc->second;
S.erase(loc->first);
}
}
q=read();
while(q--){
int x=read();
auto loc=S.lower_bound(x);
if(loc==S.end()||loc->second>x)printf("%d ",x);
else printf("%d ",loc->first);
}
puts("");
}
}
后话
是的,这么简单的代码我在考场上打挂了,因为我以为 <map>.begin() 是开始位置的前一个.....
题解做法
我感觉题解那个做法蛮有意思的,所以想着自己打一下。
基本思想和我的差不多,都是只考虑 \(l,b\),然后对可以被包含的答案进行更新。
这里具体是将每个 \(l,b,x\) 按照 \(b>x>l\) 的优先级放进数组中,然后用 multiset 维护答案。从坐标从大到小每扫到一个 \(r\) 就更新当前位置的答案,扫到 \(x\) 就加入答案,扫到 \(l\) 就把上一个位置的答案删除。
\(Code\)
struct line{
int x,type,id;
inline bool operator <(const line &w)const{
if(x==w.x)return type>w.type;//优先级设置
return x>w.x;
}
};
vector<line>a;
multiset<int>S;
int n,q,ans[200005],Q[200005];
int main(){
for(int T=read();T--;){
n=read();
S.clear();
a.clear();
for(int i=1;i<=n;i++){
int l=read(),A=read(),b=read(),r=read();
ans[i]=r;//答案先为自己
a.push_back({l,-1,i});
a.push_back({r,1,i});
}
q=read();
for(int i=1;i<=q;i++){
int x=read();
Q[i]=x;
a.push_back({x,0,i});
}
sort(a.begin(),a.end());
for(auto [x,tp,i]:a){
if(tp==1){
if(!S.empty())ans[i]=*S.rbegin();//后面答案肯定大于当前答案,更新
S.insert(ans[i]);
}else if(tp==0){
if(!S.empty())Q[i]=max(Q[i],*S.rbegin());//答案为最末答案
}else{
S.extract(ans[i]);//用 extract 是释放一个值,而用 erase 会给你全删了。
}
}
for(int i=1;i<=q;i++){
printf("%d ",Q[i]);
}
puts("");
}
}
后记
写的速度还是太慢了,正确率也不够高,要经常对着样例调代码,最后两题也还是等我之后有时间了再订正吧。

浙公网安备 33010602011771号