[61] (多校联训) A层冲刺NOIP2024模拟赛18
无论从什么意义上都能称得上挂 75 分的一场
A.选彩笔
好题
答案显然可以二分
突然发现我好像专长二分答案
钦定最大差值 \(dx\),将所有物品以 \((r,g,b)\) 看成三维空间坐标内的点,原问题可以转化成问空间里一个边长为 \(dx\) 的立方体内是否有至少 \(k\) 个点
考虑到值域不大,可以钦定立方体的一个顶点,以此来确定这个立方体,就变成了三维坐标下求给定坐标区间内点的个数问题
三维前缀和可以处理
然后说一下这个三维前缀和,之前模拟赛那个原题场考过一道 abc 的三维前缀和板子,和二维前缀和类似,简单容斥一下
\[s_{i,j,k}=s_{i-1,j,k}+s_{i,j-1,k}+s_{i,j,k-1}-s_{i-1,j-1,k}-s_{i-1,j,k-1}-s_{i,j-1,k-1}+s_{i-1,j-1,k-1}+a_{i,j,k}
\]
要说咋记这玩意,其实最好的办法应该是找规律,你会发现 \(()-1\) 的个数的奇偶性是和符号相关的,然后根据首项(两项固定项)一定为正往下顺着推就行了
以防你像 CTH 一样求出前缀和不会用:
\((i',j',k')\) 到 \((i,j,k)\) 立方体内的前缀和为:
\[s_{i,j,k}-s_{i'-1,j,k}-s_{i,j'-1,k}-s_{i,j,k'-1}+s_{i'-1,j'-1,k}+s_{i'-1,j,k'-1}+s_{i,j'-1,k'-1}-s_{i'-1,j'-1,k'-1}
\]
推法和上述还是一样的
- 为了避免前缀和式子出现负下标你可能需要特殊处理一下,我直接全都加一了
int n,K;
int maxr,maxg,maxb;
int r[100001],g[100001],b[100001];
int cnt[257][257][257];
int sum[257][257][257];
int cal(int x1,int x2,int y1,int y2,int z1,int z2){
return sum[x2][y2][z2]-sum[x1-1][y2][z2]-sum[x2][y1-1][z2]-sum[x2][y2][z1-1]+sum[x1-1][y1-1][z2]+sum[x1-1][y2][z1-1]+sum[x2][y1-1][z1-1]-sum[x1-1][y1-1][z1-1];
}
bool check(int maxdx){
for(int i=1;i<=maxr;++i){
for(int j=1;j<=maxg;++j){
for(int k=1;k<=maxb;++k){
if(cal(i,min(maxr,i+maxdx),j,min(maxg,j+maxdx),k,min(maxb,k+maxdx))>=K){
return true;
}
}
}
}
return false;
}
int main(){
scanf(n,K);
for(int i=1;i<=n;++i){
scanf(r[i],g[i],b[i]);
cnt[r[i]+1][g[i]+1][b[i]+1]++;
maxr=max(maxr,r[i]+1);
maxg=max(maxg,g[i]+1);
maxb=max(maxb,b[i]+1);
}
for(int i=1;i<=maxr;++i){
for(int j=1;j<=maxg;++j){
for(int k=1;k<=maxb;++k){
sum[i][j][k]=sum[i-1][j][k]+sum[i][j-1][k]+sum[i][j][k-1]-sum[i-1][j-1][k]-sum[i-1][j][k-1]-sum[i][j-1][k-1]+sum[i-1][j-1][k-1]+cnt[i][j][k];
}
}
}
int l=0,r=max({maxr,maxg,maxb}),ans=r;
while(l<=r){
int mid=(l+r)/2;
if(check(mid)){
r=mid-1;
ans=mid;
}
else l=mid+1;
}
cout<<ans;
}
B.兵蚁排序
也是成功挂上分了
注意到可交换次数高达 \(n^2\) 次,考虑冒泡排序,每次交换邻项元素
因为我们交换的方式是基于值排序,因此
- 将一个值与其前面的值交换,当且仅当该值小于(等于)前方的值
- 将一个值与其后面的值交换,当且仅当该值大于(等于)前方的值
按冒泡排序的思路暴力将 \(a\) 数组交换到 \(b\) 数组,若存在无法到达的目标位置则报告无解
跑出来和大样例一模一样,看来 std 也是这么写的
UPD: 关于什么是冒泡排序的思路
从前到后枚举每个值,然后寻找应该被挪到哪个位置(注意到这个位置一定在它后面,因为它前面的值已经归位了),然后通过暴力交换换过去
int n;
int a[1001],b[1001];
vector<pair<int,int>>op;
bool move(int pos,int to){
for(int i=pos;i>to;--i){
if(a[i-1]>=a[i]){
swap(a[i],a[i-1]);
op.push_back({i-1,i});
}
else{
return false;
}
}
return true;
}
int main(){
freopen("sort.in","r",stdin);
freopen("sort.out","w",stdout);
int cases;
scanf(cases);
while(cases--){
op.clear();
scanf(n);
for(int i=1;i<=n;++i){
scanf(a[i]);
}
for(int i=1;i<=n;++i){
scanf(b[i]);
}
bool flag=false;
for(int i=1;i<=n;++i){
for(int j=i;j<=n;++j){
if(a[j]==b[i]){
if(move(j,i));
else{
flag=true;
break;
}
}
}
if(flag) break;
}
if(flag) cout<<-1<<'\n';
else{
cout<<0<<'\n';
cout<<op.size()<<'\n';
for(pair<int,int>i:op){
cout<<i.first<<" "<<i.second<<'\n';
}
}
}
}
赛时 checker
这一份没法判无解
/**
*
* ------ checker for T2 ------*/
#include<bits/stdc++.h>
using namespace std;
int cases;
int n;
int a[100001],b[100001];
int main(){
ifstream in("sort.in"),out("sort.out");
in>>cases;for(int p=1;p<=cases;++p){
in>>n;
for(int i=1;i<=n;++i){
in>>a[i];
}
for(int i=1;i<=n;++i){
in>>b[i];
}
int opt;
out>>opt;
if(opt==-1){
cout<<"Testcase "<<p<<" : no solution exist"<<endl;
}
else{
int m,x,y;out>>m;
if(m>n*n){
cout<<"Testcase "<<p<<" : TOO MUCH OPERATIONS"<<endl;
continue;
}
while(m--){
out>>x>>y;
swap(a[x],a[y]);
}
int flag=0;
for(int i=1;i<=n;++i){
if(a[i]!=b[i]){
flag=i;
break;
}
}
if(flag){
cout<<"Testcase "<<p<<" : FIND WRONG ANSWER ON PLACE "<<flag<<endl;
for(int i=1;i<=n;++i){
cout<<a[i]<<" ";
}
cout<<endl;
for(int i=1;i<=n;++i){
cout<<b[i]<<" ";
}
cout<<endl;
}
else cout<<"Testcase "<<p<<" : answer is correct"<<endl;
}
}
}
C.人口局 DBA
在做了