[省选联考 2025] 幸运数字 P11830
论一个 CSP 从来没有拿过省一的fw怎么A掉 Day1T1。
先看特殊性质 A,我们只需要从 \(1\) 到 \(n\) 枚举,判断是不是合法即可。
假设我们枚举到了 \(x\),那么对于给定的 \(n\) 的条件,有以下三种:
1.\(r_{i,2} < x\) 的条件,即只能取到小于 \(x\) 的条件。
2.\(l_{i,2} > x\) 的条件,即只能取到大于 \(x\) 的条件。
3.\(l_{i,2} \le x \le r_{i,2}\) 的条件,即能取到 \(x\) 的条件。
对于第一种条件,我们记 \(Lmin=\sum l_{i,1}\),表示小于 \(x\) 的值最少能取多少个,\(Lmax=\sum r_{i,1}\),表示小于 \(x\) 的值最多能取多少个。
对于第二种条件,我们记 \(Rmin=\sum l_{i,1}\),表示大于 \(x\) 的值最少能取多少个,\(Rmax=\sum r_{i,1}\),表示大于 \(x\) 的值最多能取多少个。
对于第三种条件,我们记 \(Cnt=\sum r_{i,1}\),表示最多能取多少个 \(x\)。
这样就能贪心地判断 \(x\) 是否合法。
\(Cnt=0\) 时必然不合法,你都取不到这个数怎么合法?!
\(Lmin \le Rmin \le Lmax\) 或者 \(Rmin \le Lmin \le Rmax\) 时,也就是大于 \(x\) 的数量与小于 \(x\) 的数量相等时合法,这很显然。
然后就是其他的情况,我们肯定希望大于 \(x\) 的数量与小于 \(x\) 的数量的差值尽可能小。我们令 \(Cnt'=\min \{ |Lmin-Rmin|,|Lmax-Rmin|,|Lmin-Rmax|,|Lmax-Rmin|\}\),如果 \(Cnt'>Cnt\) 就不合法,如果 \(Cnt'<Cnt\) 就合法。
但是如果 \(Cnt'=Cnt\) 呢?
这时候就要注意,如果选择的 大于 \(x\) 的值的数量多于小于 \(x\) 的数量 就合法,否则不合法,这里是由于中位数的位置下取整造成的,自行理解。
如果每枚举一个数就暴力找条件,复杂度就是 \(O(n^2)\),40pts。
但是在枚举过程中会发现比 \(x\) 大的值的取值数量范围是递减的,比 \(x\) 小的值的取值数量范围是递增的,因此,我们开一个桶来记录条件,等需要的时候进行修改即可。这样一个条件最多会更新两次,所以时间复杂度 \(O(n)\),期望得分 60pts。
#include <stdio.h>
#include <vector>
#include <algorithm>
#include <vector>
using std::sort,std::vector,std::unique,std::lower_bound;
#define ll long long
#define lowbit(x) (x&(-x))
const int N=2e5+5;
vector<int> Ll[N*2],Rr[N*2];
int n;
int vis[N],L[N],R[N],x[N],y[N];
int t[N*2];
ll L1,L2,R1,R2,Cnt;
int ans=0;
ll ABS(ll x){
return x<0?-x:x;
}
ll MIN_num(ll a,ll b,ll c,ll d){
a=ABS(a),b=ABS(b),c=ABS(c),d=ABS(d);
if(a<=b&&a<=c&&a<=d) return a;
if(b<=a&&b<=c&&b<=d) return b;
if(c<=a&&c<=b&&c<=d) return c;
return d;
}
int MIN_opt(ll a,ll b,ll c,ll d){
a=ABS(a),b=ABS(b),c=ABS(c),d=ABS(d);
if(a<=b&&a<=c&&a<=d) return 1;
if(b<=a&&b<=c&&b<=d) return 2;
if(c<=a&&c<=b&&c<=d) return 3;
return 4;
}
void check(){ //判断一个数是否合法
if(!Cnt) return ;
if((L1<=R1&&R1<=L2)||(R1<=L1&&L1<=R2)) return void(++ans);
ll num=MIN_num(L1-R1,L2-R1,L1-R2,L2-R2);
int opt=MIN_opt(L1-R1,L2-R1,L1-R2,L2-R2);
if(Cnt<num) return ;
else if(Cnt>num) ++ans;
else{
if(opt==1&&L1<R1) ++ans;
if(opt==2&&L2<R1) ++ans;
if(opt==3&&L1<R2) ++ans;
if(opt==4&&L2<R2) ++ans;
}
}
int tot=0;
void sol(){
// ++tot; printf("%d ",tot);
L1=L2=R1=R2=Cnt=0;
ans=0;
scanf("%d",&n);
for(int i=1;i<=n;++i){
vis[i]=1;
scanf("%d%d%d%d",&L[i],&R[i],&x[i],&y[i]);
R1+=L[i];
R2+=R[i];
Ll[x[i]].push_back(i); //用桶存条件
Rr[y[i]].push_back(i);
}
for(int i=1;i<=n;++i){
for(auto s:Ll[i]){ //更新取值
R1-=L[s];
R2-=R[s];
Cnt+=R[s];
}
for(auto s:Rr[i-1]){
L1+=L[s];
L2+=R[s];
Cnt-=R[s];
}
// printf("%lld %lld %lld %lld %lld\n",L1,L2,R1,R2,Cnt);
check();
}
printf("%d\n",ans);
for(int i=1;i<=n;++i) Ll[i].clear(),Rr[i].clear();
}
int main(){
// freopen("lucky2.in","r",stdin);
int c,T;scanf("%d%d",&c,&T);
while(T--) sol();
}
/*
0 2
4
2 4 2 3
2 4 1 3
1 4 1 4
1 4 1 2
4
2 3 2 3
2 3 1 4
1 2 2 3
1 4 3 4
*/
这时候再考虑整个题目,发现只是把取值的值域扩大了,很容易想到对取值离散化,这样还是从 \(1\) 到 \(2n\)(离散化后最多枚举 \(2n\) 个)枚举即可。在统计答案时,如果第 \(i\) 项和第 \(i+1\) 项都合法,那么答案还要额外加上 \(a[i-1]-a[i]+1\)。
但是这样是有问题的,如下面两个条件:
1 1 1 1
1 2 999 999
离散后,因为 \(1\) 和 \(999\) 都合法,我们可能会以为 \(2\) 到 \(998\) 也都合法,但是这显然是错的,因为根本就取不到。
因此,我们还需要维护离散后能否取到的数据结构,这是要区间修改、单点查询的,树状数组即可(赛后发现完全不需要树状数组,直接差分数组就行)。
时间复杂度 \(O(n \log_2 n)\),瓶颈在于离散化,这样就能 A 掉这道题了。
赛时代码:
#include <stdio.h>
#include <algorithm>
#include <vector>
#define lowbit(x) (x&(-x))
#define ll long long
using namespace std;
const int N=200005;
int T,n,CJX_SY,tot=0,res=0,las=-1;
ll Lmin=0,Rmin=0,Lmax=0,Rmax=0,Cnt2=0;
//vector<int> ladd[N*3],radd[N*3];
int del[N*3],a[N*3],point=0;
int hl[N*3]={0},hr[N*3]={0},idl=0,idr=0;
struct edge{
int to,ne;
}ladd[N*3],radd[N*3];
void addl(int a,int b){
ladd[++idl]={b,hl[a]};
hl[a]=idl;
}
void addr(int a,int b){
radd[++idr]={b,hr[a]};
hr[a]=idr;
}
struct node{
int cnt1,cnt2,num1,num2;
}e[N];
ll mIN(ll a,ll b,ll c,ll d){
if(a<=b&&a<=c&&a<=d) return a;
if(b<=a&&b<=c&&b<=d) return b;
if(c<=b&&c<=a&&c<=d) return c;
return d;
}
int MIN(ll a,ll b,ll c,ll d){
if(a<=b&&a<=c&&a<=d) return 1;
if(b<=a&&b<=c&&b<=d) return 2;
if(c<=b&&c<=a&&c<=d) return 3;
return 4;
}
void update(int x,int k){
for(;x<=point;x+=lowbit(x)) del[x]+=k;
}
int query(int x){
int res=0;
for(;x;x-=lowbit(x)) res+=del[x];
return res;
}
void ADDsum(int x){ //加答案
++res;
if(las==x-1 && query(x)>0) res+=(a[x]-a[x-1]-1);
las=x;
}
void check(int x){
// if(Lmax>=3e9 || Rmax>=3e9) printf("%lld %lld\n",Lmax,Rmax);
if(Cnt2==0) return ;
if((Lmin<=Rmin&&Rmin<=Lmax)||(Rmin<=Lmin&&Lmin<=Rmax)) {
ADDsum(x);
return ;
}
ll C=mIN(abs(Lmin-Rmin),abs(Lmax-Rmin),abs(Lmin-Rmax),abs(Lmax-Rmax));
int opt=MIN(abs(Lmin-Rmin),abs(Lmax-Rmin),abs(Lmin-Rmax),abs(Lmax-Rmax));
if(Cnt2>C){
ADDsum(x);return ;
}
else if(Cnt2<C) return ;
else{
if(opt==1&&Lmin<Rmin) ADDsum(x);
if(opt==2&&Lmax<Rmin) ADDsum(x);
if(opt==3&&Lmin<Rmax) ADDsum(x);
if(opt==4&&Lmax<Rmax) ADDsum(x);
}
}
void sol(){
res=idl=idr=Lmin=Lmax=Rmin=Rmax=Cnt2=0;
// ++tot;
point=0;las=-1;
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d%d%d%d",&e[i].cnt1,&e[i].cnt2,&e[i].num1,&e[i].num2);
++point;a[point]=e[i].num1;
++point;a[point]=e[i].num2;
// ladd[e[i].num1].push_back(i);
// radd[e[i].num2].push_back(i);
// ADD[i]=1;
// Rmin+=(ll)e[i].cnt1;
// Rmax+=(ll)e[i].cnt2;
}
sort(a+1,a+point+1); //离散化
point=unique(a+1,a+point+1)-a-1;
for(int i=1;i<=n;++i){
int N1=lower_bound(a+1,a+point+1,e[i].num1)-a;
int N2=lower_bound(a+1,a+point+1,e[i].num2)-a;
update(N1+1,1);
update(N2+1,-1);
addl(N1,i); //这里当时为了卡常把vector换成链式前向星了
addr(N2,i);
Rmin+=(ll)e[i].cnt1;
Rmax+=(ll)e[i].cnt2;
}
for(int x=1;x<=point;++x){
for(int i=hl[x];i;i=ladd[i].ne){
int j=ladd[i].to;
Cnt2+=(ll)e[j].cnt2;
Rmin-=(ll)e[j].cnt1;
Rmax-=(ll)e[j].cnt2;
}
for(int i=hr[x-1];i;i=radd[i].ne){
int j=radd[i].to;
Cnt2-=(ll)e[j].cnt2;
Lmin+=(ll)e[j].cnt1;
Lmax+=(ll)e[j].cnt2;
}
check(x);
}
printf("%d\n",res);
for(int i=1;i<=point;++i) hl[i]=hr[i]=del[i]=0;
}
int main(){
// freopen("lucky.in","r",stdin);
// freopen("lucky.out","w",stdout);
scanf("%d%d",&CJX_SY,&T);
while(T--) sol();
return 0;
}

浙公网安备 33010602011771号